various fixes (#706)
This commit is contained in:
Vendored
+1
-1
@@ -22,7 +22,7 @@ Depends: ${python3:Depends},
|
||||
python3-unicodecsv,
|
||||
python3-yaml,
|
||||
${misc:Depends}
|
||||
Suggests: python3-elasticsearch, python3-fiona, python3-geojson, python3-pygeometa, python3-pyproj, python3-rasterio
|
||||
Suggests: python3-babel, python3-elasticsearch, python3-fiona, python3-geojson, python3-pygeometa, python3-pyproj, python3-rasterio
|
||||
Description: pygeoapi provides an API to geospatial data.
|
||||
.
|
||||
This package the pygeoapi module for Python 3.
|
||||
|
||||
+1
-1
@@ -198,7 +198,7 @@ resources:
|
||||
options:
|
||||
DATA_ENCODING: COMPLEX_PACKING
|
||||
format:
|
||||
name: GRIB2
|
||||
name: GRIB
|
||||
mimetype: application/x-grib2
|
||||
|
||||
test-data:
|
||||
|
||||
+132
-72
@@ -33,32 +33,29 @@
|
||||
Returns content from plugins and sets responses.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
from collections import OrderedDict
|
||||
from copy import deepcopy
|
||||
from datetime import datetime, timezone
|
||||
from functools import partial
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import uuid
|
||||
import re
|
||||
import asyncio
|
||||
from typing import Any, Tuple, Union
|
||||
import urllib.parse
|
||||
from copy import deepcopy
|
||||
from typing import Union, Any
|
||||
from collections import OrderedDict
|
||||
import uuid
|
||||
|
||||
from dateutil.parser import parse as dateparse
|
||||
from shapely.wkt import loads as shapely_loads
|
||||
from shapely.errors import WKTReadingError
|
||||
import pytz
|
||||
from shapely.errors import WKTReadingError
|
||||
from shapely.wkt import loads as shapely_loads
|
||||
|
||||
from pygeoapi import __version__
|
||||
from pygeoapi import l10n
|
||||
from pygeoapi import __version__, l10n
|
||||
from pygeoapi.linked_data import (geojson2geojsonld, jsonldify,
|
||||
jsonldify_collection)
|
||||
from pygeoapi.log import setup_logger
|
||||
from pygeoapi.process.base import (
|
||||
ProcessorExecuteError
|
||||
)
|
||||
from pygeoapi.process.base import ProcessorExecuteError
|
||||
from pygeoapi.plugin import load_plugin, PLUGINS
|
||||
from pygeoapi.provider.base import (
|
||||
ProviderGenericError, ProviderConnectionError, ProviderNotFoundError,
|
||||
@@ -68,6 +65,7 @@ from pygeoapi.provider.base import (
|
||||
from pygeoapi.provider.tile import (ProviderTileNotFoundError,
|
||||
ProviderTileQueryError,
|
||||
ProviderTilesetIdNotFoundError)
|
||||
|
||||
from pygeoapi.util import (dategetter, DATETIME_FORMAT,
|
||||
filter_dict_by_key_value, get_provider_by_type,
|
||||
get_provider_default, get_typed_value, JobStatus,
|
||||
@@ -119,7 +117,8 @@ OGC_RELTYPES_BASE = 'http://www.opengis.net/def/rel/ogc/1.0'
|
||||
|
||||
|
||||
def pre_process(func):
|
||||
""" Decorator that transforms an incoming Request instance specific to the
|
||||
"""
|
||||
Decorator that transforms an incoming Request instance specific to the
|
||||
web framework (i.e. Flask or Starlette) into a generic :class:`APIRequest`
|
||||
instance.
|
||||
|
||||
@@ -140,7 +139,8 @@ def pre_process(func):
|
||||
|
||||
|
||||
class APIRequest:
|
||||
""" Transforms an incoming server-specific Request into an object
|
||||
"""
|
||||
Transforms an incoming server-specific Request into an object
|
||||
with some generic helper methods and properties.
|
||||
|
||||
.. note:: Typically, this instance is created automatically by the
|
||||
@@ -229,7 +229,8 @@ class APIRequest:
|
||||
|
||||
@classmethod
|
||||
def with_data(cls, request, supported_locales) -> 'APIRequest':
|
||||
""" Factory class method to create an `APIRequest` instance with data.
|
||||
"""
|
||||
Factory class method to create an `APIRequest` instance with data.
|
||||
|
||||
If the request body is required, an `APIRequest` should always be
|
||||
instantiated using this class method. The reason for this is, that the
|
||||
@@ -242,6 +243,7 @@ class APIRequest:
|
||||
:param supported_locales: List or set of supported Locale instances.
|
||||
:returns: An `APIRequest` instance with data.
|
||||
"""
|
||||
|
||||
api_req = cls(request, supported_locales)
|
||||
if hasattr(request, 'data'):
|
||||
# Set data from Flask request
|
||||
@@ -256,11 +258,13 @@ class APIRequest:
|
||||
|
||||
@staticmethod
|
||||
def _get_params(request):
|
||||
""" Extracts the query parameters from the `Request` object.
|
||||
"""
|
||||
Extracts the query parameters from the `Request` object.
|
||||
|
||||
:param request: A Flask or Starlette Request instance
|
||||
:returns: `ImmutableMultiDict` or empty `dict`
|
||||
:returns: `ImmutableMultiDict` or empty `dict`
|
||||
"""
|
||||
|
||||
if hasattr(request, 'args'):
|
||||
# Return ImmutableMultiDict from Flask request
|
||||
return request.args
|
||||
@@ -271,14 +275,16 @@ class APIRequest:
|
||||
return {}
|
||||
|
||||
def _get_locale(self, headers, supported_locales):
|
||||
""" Detects locale from "lang=<language>" param or `Accept-Language`
|
||||
"""
|
||||
Detects locale from "lang=<language>" param or `Accept-Language`
|
||||
header. Returns a tuple of (raw, locale) if found in params or headers.
|
||||
Returns a tuple of (raw default, default locale) if not found.
|
||||
|
||||
:param headers: A dict with Request headers
|
||||
:param supported_locales: List or set of supported Locale instances
|
||||
:returns: A tuple of (str, Locale)
|
||||
:param headers: A dict with Request headers
|
||||
:param supported_locales: List or set of supported Locale instances
|
||||
:returns: A tuple of (str, Locale)
|
||||
"""
|
||||
|
||||
raw = None
|
||||
try:
|
||||
default_locale = l10n.str2locale(supported_locales[0])
|
||||
@@ -310,7 +316,7 @@ class APIRequest:
|
||||
Get `Request` format type from query parameters or headers.
|
||||
|
||||
:param headers: Dict of Request headers
|
||||
:returns: format value or None if not found/specified
|
||||
:returns: format value or None if not found/specified
|
||||
"""
|
||||
|
||||
# Optional f=html or f=json query param
|
||||
@@ -333,22 +339,23 @@ class APIRequest:
|
||||
|
||||
@property
|
||||
def data(self) -> bytes:
|
||||
""" Returns the additional data send with the Request (bytes). """
|
||||
"""Returns the additional data send with the Request (bytes)"""
|
||||
return self._data
|
||||
|
||||
@property
|
||||
def params(self):
|
||||
""" Returns the Request query parameters dict. """
|
||||
"""Returns the Request query parameters dict"""
|
||||
return self._args
|
||||
|
||||
@property
|
||||
def path_info(self):
|
||||
""" Returns the web server request path info part. """
|
||||
"""Returns the web server request path info part"""
|
||||
return self._path_info
|
||||
|
||||
@property
|
||||
def locale(self) -> l10n.Locale:
|
||||
""" Returns the user-defined locale from the request object.
|
||||
"""
|
||||
Returns the user-defined locale from the request object.
|
||||
If no locale has been defined or if it is invalid,
|
||||
the default server locale is returned.
|
||||
|
||||
@@ -360,34 +367,40 @@ class APIRequest:
|
||||
to the :func:`l10n.get_plugin_locale` function, so that
|
||||
the best match for the provider can be determined.
|
||||
|
||||
:returns: babel.core.Locale
|
||||
:returns: babel.core.Locale
|
||||
"""
|
||||
|
||||
return self._locale
|
||||
|
||||
@property
|
||||
def raw_locale(self) -> Union[str, None]:
|
||||
""" Returns the raw locale string from the `Request` object.
|
||||
"""
|
||||
Returns the raw locale string from the `Request` object.
|
||||
If no "lang" query parameter or `Accept-Language` header was found,
|
||||
`None` is returned.
|
||||
Pass this value to the :func:`l10n.get_plugin_locale` function to let
|
||||
the provider determine a best match for the locale, which may be
|
||||
different from the locale used by pygeoapi's UI.
|
||||
|
||||
:returns: a locale string or None
|
||||
:returns: a locale string or None
|
||||
"""
|
||||
|
||||
return self._raw_locale
|
||||
|
||||
@property
|
||||
def format(self) -> Union[str, None]:
|
||||
""" Returns the content type format from the
|
||||
"""
|
||||
Returns the content type format from the
|
||||
request query parameters or headers.
|
||||
|
||||
:returns: Format name or None
|
||||
:returns: Format name or None
|
||||
"""
|
||||
|
||||
return self._format
|
||||
|
||||
def get_linkrel(self, format_: str) -> str:
|
||||
""" Returns the hyperlink relationship (rel) attribute value for
|
||||
"""
|
||||
Returns the hyperlink relationship (rel) attribute value for
|
||||
the given API format string.
|
||||
|
||||
The string is compared against the request format and if it matches,
|
||||
@@ -396,25 +409,28 @@ class APIRequest:
|
||||
the relationship 'self' is returned as well (JSON is the default).
|
||||
|
||||
:param format_: The format to compare the request format against.
|
||||
:returns: A string 'self' or 'alternate'.
|
||||
:returns: A string 'self' or 'alternate'.
|
||||
"""
|
||||
|
||||
fmt = format_.lower()
|
||||
if fmt == self._format or (fmt == F_JSON and not self._format):
|
||||
return 'self'
|
||||
return 'alternate'
|
||||
|
||||
def is_valid(self, additional_formats=None) -> bool:
|
||||
""" Returns True if:
|
||||
|
||||
"""
|
||||
Returns True if:
|
||||
- the format is not set (None)
|
||||
- the requested format is supported
|
||||
- the requested format exists in a list if additional formats
|
||||
|
||||
.. note:: Format names are matched in a case-insensitive manner.
|
||||
|
||||
:param additional_formats: Optional additional supported formats list
|
||||
:returns: A boolean
|
||||
:param additional_formats: Optional additional supported formats list
|
||||
|
||||
:returns: bool
|
||||
"""
|
||||
|
||||
if not self._format:
|
||||
return True
|
||||
if self._format in FORMAT_TYPES.keys():
|
||||
@@ -425,7 +441,8 @@ class APIRequest:
|
||||
|
||||
def get_response_headers(self, force_lang: l10n.Locale = None,
|
||||
force_type: str = None) -> dict:
|
||||
""" Prepares and returns a dictionary with Response object headers.
|
||||
"""
|
||||
Prepares and returns a dictionary with Response object headers.
|
||||
|
||||
This method always adds a 'Content-Language' header, where the value
|
||||
is determined by the 'lang' query parameter or 'Accept-Language'
|
||||
@@ -444,10 +461,11 @@ class APIRequest:
|
||||
If an API response always needs to be in the same
|
||||
language, 'force_lang' should be set to that language.
|
||||
|
||||
:param force_lang: An optional Content-Language header override.
|
||||
:param force_type: An optional Content-Type header override.
|
||||
:returns: A header dict
|
||||
:param force_lang: An optional Content-Language header override.
|
||||
:param force_type: An optional Content-Type header override.
|
||||
:returns: A header dict
|
||||
"""
|
||||
|
||||
headers = HEADERS.copy()
|
||||
l10n.set_response_language(headers, force_lang or self._locale)
|
||||
if force_type:
|
||||
@@ -505,9 +523,10 @@ class API:
|
||||
|
||||
@pre_process
|
||||
@jsonldify
|
||||
def landing_page(self, request: Union[APIRequest, Any]):
|
||||
def landing_page(self,
|
||||
request: Union[APIRequest, Any]) -> Tuple[dict, int, str]:
|
||||
"""
|
||||
Provide API
|
||||
Provide API landing page
|
||||
|
||||
:param request: A request object
|
||||
|
||||
@@ -594,7 +613,8 @@ class API:
|
||||
return headers, 200, to_json(fcm, self.pretty_print)
|
||||
|
||||
@pre_process
|
||||
def openapi(self, request: Union[APIRequest, Any], openapi):
|
||||
def openapi(self, request: Union[APIRequest, Any],
|
||||
openapi) -> Tuple[dict, int, str]:
|
||||
"""
|
||||
Provide OpenAPI document
|
||||
|
||||
@@ -626,7 +646,8 @@ class API:
|
||||
return headers, 200, openapi.read()
|
||||
|
||||
@pre_process
|
||||
def conformance(self, request: Union[APIRequest, Any]):
|
||||
def conformance(self,
|
||||
request: Union[APIRequest, Any]) -> Tuple[dict, int, str]:
|
||||
"""
|
||||
Provide conformance definition
|
||||
|
||||
@@ -652,7 +673,8 @@ class API:
|
||||
|
||||
@pre_process
|
||||
@jsonldify
|
||||
def describe_collections(self, request: Union[APIRequest, Any], dataset=None): # noqa
|
||||
def describe_collections(self, request: Union[APIRequest, Any],
|
||||
dataset=None) -> Tuple[dict, int, str]:
|
||||
"""
|
||||
Provide collection metadata
|
||||
|
||||
@@ -996,7 +1018,8 @@ class API:
|
||||
|
||||
@pre_process
|
||||
@jsonldify
|
||||
def get_collection_queryables(self, request: Union[APIRequest, Any], dataset=None): # noqa
|
||||
def get_collection_queryables(self, request: Union[APIRequest, Any],
|
||||
dataset=None) -> Tuple[dict, int, str]:
|
||||
"""
|
||||
Provide collection queryables
|
||||
|
||||
@@ -1413,7 +1436,8 @@ class API:
|
||||
return headers, 200, to_json(content, self.pretty_print)
|
||||
|
||||
@pre_process
|
||||
def get_collection_item(self, request: Union[APIRequest, Any], dataset, identifier): # noqa
|
||||
def get_collection_item(self, request: Union[APIRequest, Any],
|
||||
dataset, identifier) -> Tuple[dict, int, str]:
|
||||
"""
|
||||
Get a single collection item
|
||||
|
||||
@@ -1553,7 +1577,8 @@ class API:
|
||||
|
||||
@pre_process
|
||||
@jsonldify
|
||||
def get_collection_coverage(self, request: Union[APIRequest, Any], dataset): # noqa
|
||||
def get_collection_coverage(self, request: Union[APIRequest, Any],
|
||||
dataset) -> Tuple[dict, int, str]:
|
||||
"""
|
||||
Returns a subset of a collection coverage
|
||||
|
||||
@@ -1692,7 +1717,9 @@ class API:
|
||||
|
||||
@pre_process
|
||||
@jsonldify
|
||||
def get_collection_coverage_domainset(self, request: Union[APIRequest, Any], dataset): # noqa
|
||||
def get_collection_coverage_domainset(
|
||||
self, request: Union[APIRequest, Any],
|
||||
dataset) -> Tuple[dict, int, str]:
|
||||
"""
|
||||
Returns a collection coverage domainset
|
||||
|
||||
@@ -1743,7 +1770,9 @@ class API:
|
||||
|
||||
@pre_process
|
||||
@jsonldify
|
||||
def get_collection_coverage_rangetype(self, request: Union[APIRequest, Any], dataset): # noqa
|
||||
def get_collection_coverage_rangetype(
|
||||
self, request: Union[APIRequest, Any],
|
||||
dataset) -> Tuple[dict, int, str]:
|
||||
"""
|
||||
Returns a collection coverage rangetype
|
||||
|
||||
@@ -1793,7 +1822,8 @@ class API:
|
||||
|
||||
@pre_process
|
||||
@jsonldify
|
||||
def get_collection_tiles(self, request: Union[APIRequest, Any], dataset=None): # noqa
|
||||
def get_collection_tiles(self, request: Union[APIRequest, Any],
|
||||
dataset=None) -> Tuple[dict, int, str]:
|
||||
"""
|
||||
Provide collection tiles
|
||||
|
||||
@@ -1896,9 +1926,10 @@ class API:
|
||||
|
||||
@pre_process
|
||||
@jsonldify
|
||||
def get_collection_tiles_data(self, request: Union[APIRequest, Any],
|
||||
dataset=None, matrix_id=None,
|
||||
z_idx=None, y_idx=None, x_idx=None):
|
||||
def get_collection_tiles_data(
|
||||
self, request: Union[APIRequest, Any],
|
||||
dataset=None, matrix_id=None,
|
||||
z_idx=None, y_idx=None, x_idx=None) -> Tuple[dict, int, str]:
|
||||
"""
|
||||
Get collection items tiles
|
||||
|
||||
@@ -1979,8 +2010,9 @@ class API:
|
||||
|
||||
@pre_process
|
||||
@jsonldify
|
||||
def get_collection_tiles_metadata(self, request: Union[APIRequest, Any],
|
||||
dataset=None, matrix_id=None):
|
||||
def get_collection_tiles_metadata(
|
||||
self, request: Union[APIRequest, Any],
|
||||
dataset=None, matrix_id=None) -> Tuple[dict, int, str]:
|
||||
"""
|
||||
Get collection items tiles
|
||||
|
||||
@@ -2060,7 +2092,8 @@ class API:
|
||||
|
||||
@pre_process
|
||||
@jsonldify
|
||||
def describe_processes(self, request: Union[APIRequest, Any], process=None): # noqa
|
||||
def describe_processes(self, request: Union[APIRequest, Any],
|
||||
process=None) -> Tuple[dict, int, str]:
|
||||
"""
|
||||
Provide processes metadata
|
||||
|
||||
@@ -2152,7 +2185,8 @@ class API:
|
||||
return headers, 200, to_json(response, self.pretty_print)
|
||||
|
||||
@pre_process
|
||||
def get_process_jobs(self, request: Union[APIRequest, Any], process_id, job_id=None): # noqa
|
||||
def get_process_jobs(self, request: Union[APIRequest, Any],
|
||||
process_id, job_id=None) -> Tuple[dict, int, str]:
|
||||
"""
|
||||
Get process jobs
|
||||
|
||||
@@ -2255,7 +2289,8 @@ class API:
|
||||
return headers, 200, to_json(serialized_jobs, self.pretty_print)
|
||||
|
||||
@pre_process
|
||||
def execute_process(self, request: Union[APIRequest, Any], process_id):
|
||||
def execute_process(self, request: Union[APIRequest, Any],
|
||||
process_id) -> Tuple[dict, int, str]:
|
||||
"""
|
||||
Execute process
|
||||
|
||||
@@ -2372,7 +2407,8 @@ class API:
|
||||
return headers, http_status, to_json(response, self.pretty_print)
|
||||
|
||||
@pre_process
|
||||
def get_process_job_result(self, request: Union[APIRequest, Any], process_id, job_id): # noqa
|
||||
def get_process_job_result(self, request: Union[APIRequest, Any],
|
||||
process_id, job_id) -> Tuple[dict, int, str]:
|
||||
"""
|
||||
Get result of job (instance of a process)
|
||||
|
||||
@@ -2454,8 +2490,10 @@ class API:
|
||||
|
||||
return headers, 200, content
|
||||
|
||||
def delete_process_job(self, process_id, job_id):
|
||||
def delete_process_job(self, process_id, job_id) -> Tuple[dict, int, str]:
|
||||
"""
|
||||
Delete a process job
|
||||
|
||||
:param process_id: process identifier
|
||||
:param job_id: job identifier
|
||||
|
||||
@@ -2493,14 +2531,17 @@ class API:
|
||||
return {}, http_status, response
|
||||
|
||||
@pre_process
|
||||
def get_collection_edr_query(self, request: Union[APIRequest, Any],
|
||||
dataset, instance, query_type):
|
||||
def get_collection_edr_query(
|
||||
self, request: Union[APIRequest, Any],
|
||||
dataset, instance, query_type) -> Tuple[dict, int, str]:
|
||||
"""
|
||||
Queries collection EDR
|
||||
|
||||
:param request: APIRequest instance with query params
|
||||
:param dataset: dataset name
|
||||
:param instance: instance name
|
||||
:param query_type: EDR query type
|
||||
|
||||
:returns: tuple of headers, status code, content
|
||||
"""
|
||||
|
||||
@@ -2616,7 +2657,15 @@ class API:
|
||||
|
||||
@pre_process
|
||||
@jsonldify
|
||||
def get_stac_root(self, request: Union[APIRequest, Any]):
|
||||
def get_stac_root(
|
||||
self, request: Union[APIRequest, Any]) -> Tuple[dict, int, str]:
|
||||
"""
|
||||
Provide STAC root page
|
||||
|
||||
:param request: APIRequest instance with query params
|
||||
|
||||
:returns: tuple of headers, status code, content
|
||||
"""
|
||||
|
||||
if not request.is_valid():
|
||||
return self.get_format_exception(request)
|
||||
@@ -2663,7 +2712,15 @@ class API:
|
||||
|
||||
@pre_process
|
||||
@jsonldify
|
||||
def get_stac_path(self, request: Union[APIRequest, Any], path):
|
||||
def get_stac_path(self, request: Union[APIRequest, Any],
|
||||
path) -> Tuple[dict, int, str]:
|
||||
"""
|
||||
Provide STAC resource path
|
||||
|
||||
:param request: APIRequest instance with query params
|
||||
|
||||
:returns: tuple of headers, status code, content
|
||||
"""
|
||||
|
||||
if not request.is_valid():
|
||||
return self.get_format_exception(request)
|
||||
@@ -2744,7 +2801,8 @@ class API:
|
||||
headers.pop('Content-Type', None)
|
||||
return headers, 200, stac_data
|
||||
|
||||
def get_exception(self, status, headers, format_, code, description):
|
||||
def get_exception(self, status, headers, format_, code,
|
||||
description) -> Tuple[dict, int, str]:
|
||||
"""
|
||||
Exception handler
|
||||
|
||||
@@ -2772,13 +2830,15 @@ class API:
|
||||
|
||||
return headers, status, content
|
||||
|
||||
def get_format_exception(self, request):
|
||||
""" Returns a format exception.
|
||||
def get_format_exception(self, request) -> Tuple[dict, int, str]:
|
||||
"""
|
||||
Returns a format exception.
|
||||
|
||||
:param request: An APIRequest instance.
|
||||
|
||||
:returns: tuple of (headers, status, message)
|
||||
:returns: tuple of (headers, status, message)
|
||||
"""
|
||||
|
||||
# Content-Language is in the system locale (ignore language settings)
|
||||
headers = request.get_response_headers(SYSTEM_LOCALE)
|
||||
msg = f'Invalid format: {request.format}'
|
||||
@@ -2786,7 +2846,7 @@ class API:
|
||||
400, headers, F_JSON, 'InvalidParameterValue', msg)
|
||||
|
||||
|
||||
def validate_bbox(value=None):
|
||||
def validate_bbox(value=None) -> list:
|
||||
"""
|
||||
Helper function to validate bbox parameter
|
||||
|
||||
@@ -2822,7 +2882,7 @@ def validate_bbox(value=None):
|
||||
return bbox
|
||||
|
||||
|
||||
def validate_datetime(resource_def, datetime_=None):
|
||||
def validate_datetime(resource_def, datetime_=None) -> str:
|
||||
"""
|
||||
Helper function to validate temporal parameter
|
||||
|
||||
|
||||
@@ -97,14 +97,18 @@ if (OGC_SCHEMAS_LOCATION is not None and
|
||||
|
||||
|
||||
def get_response(result: tuple):
|
||||
""" Creates a Flask Response object and updates matching headers.
|
||||
|
||||
:param result: The result of the API call.
|
||||
This should be a tuple of (headers, status, content).
|
||||
:returns: A Response instance.
|
||||
"""
|
||||
Creates a Flask Response object and updates matching headers.
|
||||
|
||||
:param result: The result of the API call.
|
||||
This should be a tuple of (headers, status, content).
|
||||
|
||||
:returns: A Response instance.
|
||||
"""
|
||||
|
||||
headers, status, content = result
|
||||
response = make_response(content, status)
|
||||
|
||||
if headers:
|
||||
response.headers = headers
|
||||
return response
|
||||
|
||||
+98
-62
@@ -54,17 +54,21 @@ class LocaleError(Exception):
|
||||
|
||||
|
||||
def str2locale(value, silent: bool = False) -> Union[Locale, None]:
|
||||
""" Converts a web locale or language tag into a Babel Locale instance.
|
||||
"""
|
||||
Converts a web locale or language tag into a Babel Locale instance.
|
||||
|
||||
.. note:: If `value` already is a Locale, it is returned as-is.
|
||||
|
||||
:param value: A string containing a (web) locale (e.g. 'fr-CH')
|
||||
:param value: A string containing a (web) locale (e.g. 'fr-CH')
|
||||
or language tag (e.g. 'de').
|
||||
:param silent: If True (default = False), no errors will be raised
|
||||
when parsing failed. Instead, `None` will be returned.
|
||||
:returns: babel.core.Locale or None
|
||||
:raises: LocaleError
|
||||
:param silent: If True (default = False), no errors will be raised
|
||||
when parsing failed. Instead, `None` will be returned.
|
||||
|
||||
:returns: babel.core.Locale or None
|
||||
|
||||
:raises: LocaleError
|
||||
"""
|
||||
|
||||
if isinstance(value, Locale):
|
||||
return value
|
||||
|
||||
@@ -75,10 +79,12 @@ def str2locale(value, silent: bool = False) -> Union[Locale, None]:
|
||||
|
||||
try:
|
||||
loc = Locale.parse(value.strip().replace('-', '_'))
|
||||
except (ValueError, AttributeError):
|
||||
except (ValueError, AttributeError) as err:
|
||||
LOGGER.warning(err)
|
||||
if not silent:
|
||||
raise LocaleError(f"invalid locale '{value}'")
|
||||
except _UnknownLocaleError as err:
|
||||
LOGGER.warning(err)
|
||||
if not silent:
|
||||
raise LocaleError(err)
|
||||
else:
|
||||
@@ -89,20 +95,25 @@ def str2locale(value, silent: bool = False) -> Union[Locale, None]:
|
||||
|
||||
|
||||
def locale2str(value: Locale) -> str:
|
||||
""" Converts a Babel Locale instance into a web locale string.
|
||||
|
||||
:param value: babel.core.Locale
|
||||
:returns: A string containing a web locale (e.g. 'fr-CH')
|
||||
or language tag (e.g. 'de').
|
||||
:raises: LocaleError
|
||||
"""
|
||||
Converts a Babel Locale instance into a web locale string.
|
||||
|
||||
:param value: babel.core.Locale
|
||||
|
||||
:returns: A string containing a web locale (e.g. 'fr-CH')
|
||||
or language tag (e.g. 'de').
|
||||
|
||||
:raises: LocaleError
|
||||
"""
|
||||
|
||||
if not isinstance(value, Locale):
|
||||
raise LocaleError(f"'{value}' is not of type {Locale.__name__}")
|
||||
return str(value).replace('_', '-')
|
||||
|
||||
|
||||
def best_match(accept_languages, available_locales) -> Locale:
|
||||
""" Takes an Accept-Languages string (from header or request query params)
|
||||
"""
|
||||
Takes an Accept-Languages string (from header or request query params)
|
||||
and finds the best matching locale from a list of available locales.
|
||||
|
||||
This function provides a framework-independent alternative to the
|
||||
@@ -120,19 +131,21 @@ def best_match(accept_languages, available_locales) -> Locale:
|
||||
or unknown locale is ignored. However, if no
|
||||
`available_locales` are specified, a `LocaleError` is raised.
|
||||
|
||||
:param accept_languages: A Locale or string with one or more languages.
|
||||
This can be as simple as "de" for example,
|
||||
but it's also possible to include a territory
|
||||
(e.g. "en-US" or "fr_BE") or even a complex
|
||||
string with quality values, e.g.
|
||||
"fr-CH, fr;q=0.9, en;q=0.8, de;q=0.7, *;q=0.5".
|
||||
:param available_locales: A list containing the available locales.
|
||||
For example, a pygeoapi provider might only
|
||||
support ["de", "en"].
|
||||
Locales in the list can be specified as strings
|
||||
(e.g. "nl-NL") or `Locale` instances.
|
||||
:returns: babel.core.Locale
|
||||
:raises: LocaleError
|
||||
:param accept_languages: A Locale or string with one or more languages.
|
||||
This can be as simple as "de" for example,
|
||||
but it's also possible to include a territory
|
||||
(e.g. "en-US" or "fr_BE") or even a complex
|
||||
string with quality values, e.g.
|
||||
"fr-CH, fr;q=0.9, en;q=0.8, de;q=0.7, *;q=0.5".
|
||||
:param available_locales: A list containing the available locales.
|
||||
For example, a pygeoapi provider might only
|
||||
support ["de", "en"].
|
||||
Locales in the list can be specified as strings
|
||||
(e.g. "nl-NL") or `Locale` instances.
|
||||
|
||||
:returns: babel.core.Locale
|
||||
|
||||
:raises: LocaleError
|
||||
"""
|
||||
|
||||
def get_match(locale_, available_locales_):
|
||||
@@ -158,6 +171,7 @@ def best_match(accept_languages, available_locales) -> Locale:
|
||||
if isinstance(accept_languages, Locale):
|
||||
# If a Babel Locale was used as input, transform back into a string
|
||||
accept_languages = locale2str(accept_languages)
|
||||
|
||||
if not isinstance(accept_languages, str):
|
||||
# If `accept_languages` is not a string, ignore it
|
||||
LOGGER.debug(f"ignoring invalid accept-languages '{accept_languages}'")
|
||||
@@ -234,12 +248,15 @@ def translate(value, language: Union[Locale, str]):
|
||||
|
||||
If `language` is not a string or Locale, a LocaleError is raised.
|
||||
|
||||
:param value: A value to translate. Typically either a string or
|
||||
a language struct dictionary.
|
||||
:param language: A locale string (e.g. "en-US" or "en") or Babel Locale.
|
||||
:returns: A translated string or the original value.
|
||||
:raises: LocaleError
|
||||
:param value: A value to translate. Typically either a string or
|
||||
a language struct dictionary.
|
||||
:param language: A locale string (e.g. "en-US" or "en") or Babel Locale.
|
||||
|
||||
:returns: A translated string or the original value.
|
||||
|
||||
:raises: LocaleError
|
||||
"""
|
||||
|
||||
nested_dicts = isinstance(value, dict) and any(isinstance(v, dict)
|
||||
for v in value.values())
|
||||
if not isinstance(value, dict) or nested_dicts:
|
||||
@@ -274,16 +291,18 @@ def translate(value, language: Union[Locale, str]):
|
||||
|
||||
|
||||
def translate_struct(struct, locale_: Locale, is_config: bool = False):
|
||||
""" Returns a copy of a given dict or list, where all language structs
|
||||
"""
|
||||
Returns a copy of a given dict or list, where all language structs
|
||||
are filtered on the given locale, i.e. all language structs are replaced
|
||||
by translated values for the best matching locale.
|
||||
|
||||
:param struct: A dict or list (of dicts) to filter/translate.
|
||||
:param locale_: The Babel Locale to filter on.
|
||||
:param is_config: If True, the struct is treated as a pygeoapi config.
|
||||
This means that the first 2 levels won't be translated
|
||||
and the translated struct is cached for speed.
|
||||
:returns: A translated dict or list
|
||||
:param struct: A dict or list (of dicts) to filter/translate.
|
||||
:param locale_: The Babel Locale to filter on.
|
||||
:param is_config: If True, the struct is treated as a pygeoapi config.
|
||||
This means that the first 2 levels won't be translated
|
||||
and the translated struct is cached for speed.
|
||||
|
||||
:returns: A translated dict or list
|
||||
"""
|
||||
|
||||
def _translate_dict(obj, level: int = 0):
|
||||
@@ -335,8 +354,9 @@ def locale_from_headers(headers) -> str:
|
||||
|
||||
:param headers: Mapping of request headers.
|
||||
|
||||
:returns: locale string or None
|
||||
:returns: locale string or None
|
||||
"""
|
||||
|
||||
lang = {k.lower(): v for k, v in headers.items()}.get('accept-language')
|
||||
if lang:
|
||||
LOGGER.debug(f"Got locale '{lang}' from 'Accept-Language' header")
|
||||
@@ -350,10 +370,11 @@ def locale_from_params(params) -> str:
|
||||
web locales (e.g. "en-US") or basic language tags (e.g. "en").
|
||||
A value of `None` is returned if the locale was not found or invalid.
|
||||
|
||||
:param params: Mapping of request query parameters.
|
||||
:param params: Mapping of request query parameters.
|
||||
|
||||
:returns: locale string or None
|
||||
:returns: locale string or None
|
||||
"""
|
||||
|
||||
lang = params.get(QUERY_PARAM)
|
||||
if lang:
|
||||
LOGGER.debug(f"Got locale '{lang}' from query parameter '{QUERY_PARAM}'") # noqa
|
||||
@@ -361,15 +382,18 @@ def locale_from_params(params) -> str:
|
||||
|
||||
|
||||
def set_response_language(headers: dict, *locale_: Locale):
|
||||
""" Sets the Content-Language on the given HTTP response headers dict.
|
||||
|
||||
:param headers: A dict of HTTP response headers.
|
||||
:param locale_: The Babel Locale(s) to which to set the
|
||||
Content-Language header.
|
||||
Multiple locales can be set for this header.
|
||||
Note that duplicates will be removed.
|
||||
:raises: LocaleError if no valid Babel Locale was found.
|
||||
"""
|
||||
Sets the Content-Language on the given HTTP response headers dict.
|
||||
|
||||
:param headers: A dict of HTTP response headers.
|
||||
:param locale_: The Babel Locale(s) to which to set the
|
||||
Content-Language header.
|
||||
Multiple locales can be set for this header.
|
||||
Note that duplicates will be removed.
|
||||
|
||||
:raises: LocaleError if no valid Babel Locale was found.
|
||||
"""
|
||||
|
||||
if not hasattr(headers, '__setitem__'):
|
||||
LOGGER.warning(f"Cannot set headers on object '{headers}'")
|
||||
return
|
||||
@@ -388,19 +412,24 @@ def set_response_language(headers: dict, *locale_: Locale):
|
||||
if not locales:
|
||||
raise LocaleError('no valid locales set')
|
||||
loc_str = ', '.join(locales)
|
||||
|
||||
LOGGER.debug(f'Setting Content-Language to {loc_str}')
|
||||
headers['Content-Language'] = loc_str
|
||||
|
||||
|
||||
def add_locale(url, locale_):
|
||||
""" Adds a locale query parameter (e.g. 'lang=en-US') to a URL.
|
||||
def add_locale(url, locale_) -> str:
|
||||
"""
|
||||
Adds a locale query parameter (e.g. 'lang=en-US') to a URL.
|
||||
If `locale_` is None or an empty string, the URL will be returned as-is.
|
||||
|
||||
:param url: The web page URL (may contain query string).
|
||||
:param url: The web page URL (may contain query string).
|
||||
:param locale_: The web locale or language tag to append to the query.
|
||||
:returns: A new URL with a 'lang=<locale>' query parameter.
|
||||
:raises: requests.exceptions.MissingSchema
|
||||
|
||||
:returns: A new URL with a 'lang=<locale>' query parameter.
|
||||
|
||||
:raises: requests.exceptions.MissingSchema
|
||||
"""
|
||||
|
||||
loc = str2locale(locale_, True)
|
||||
if not loc:
|
||||
# Validation of locale failed
|
||||
@@ -428,12 +457,15 @@ def add_locale(url, locale_):
|
||||
|
||||
|
||||
def get_locales(config: dict) -> list:
|
||||
""" Reads the configured locales/languages from the given configuration.
|
||||
"""
|
||||
Reads the configured locales/languages from the given configuration.
|
||||
The first Locale in the returned list should be the default locale.
|
||||
|
||||
:param config: A pygeaapi configuration dict
|
||||
:returns: A list of supported Locale instances
|
||||
:param config: A pygeaapi configuration dict
|
||||
|
||||
:returns: A list of supported Locale instances
|
||||
"""
|
||||
|
||||
srv_cfg = config.get('server', {})
|
||||
lang = srv_cfg.get('languages', srv_cfg.get('language', []))
|
||||
|
||||
@@ -451,16 +483,20 @@ def get_locales(config: dict) -> list:
|
||||
raise LocaleError('Bad value in supported server language(s)')
|
||||
|
||||
|
||||
def get_plugin_locale(config: dict, requested_locale: Union[str, None]) -> Union[Locale, None]: # noqa
|
||||
""" Returns the supported locale (best match) for a plugin
|
||||
def get_plugin_locale(
|
||||
config: dict,
|
||||
requested_locale: Union[str, None]) -> Union[Locale, None]:
|
||||
"""
|
||||
Returns the supported locale (best match) for a plugin
|
||||
based on the requested raw locale string.
|
||||
Returns None if the plugin does not support any locales.
|
||||
Returns the default (= first) locale that the plugin supports
|
||||
if no match for the requested locale could be found.
|
||||
|
||||
:param config: The plugin definition
|
||||
:param requested_locale: The requested locale string (or None)
|
||||
:param config: The plugin definition
|
||||
:param requested_locale: The requested locale string (or None)
|
||||
"""
|
||||
|
||||
plugin_name = f"{config.get('name', '')} plugin".strip()
|
||||
if not requested_locale:
|
||||
LOGGER.debug(f'No requested locale for {plugin_name}')
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# =================================================================
|
||||
#
|
||||
# Authors: Francesco Bartoli <xbartolone@gmail.com>
|
||||
#
|
||||
# Tom Kralidis <tomkralidis@gmail.com>
|
||||
#
|
||||
# Copyright (c) 2020 Francesco Bartoli
|
||||
# Copyright (c) 2020 Tom Kralidis
|
||||
@@ -76,14 +76,18 @@ api_ = API(CONFIG)
|
||||
|
||||
|
||||
def get_response(result: tuple) -> Response:
|
||||
""" Creates a Starlette Response object and updates matching headers.
|
||||
|
||||
:param result: The result of the API call.
|
||||
This should be a tuple of (headers, status, content).
|
||||
:returns: A Response instance.
|
||||
"""
|
||||
Creates a Starlette Response object and updates matching headers.
|
||||
|
||||
:param result: The result of the API call.
|
||||
This should be a tuple of (headers, status, content).
|
||||
|
||||
:returns: A Response instance.
|
||||
"""
|
||||
|
||||
headers, status, content = result
|
||||
response = Response(content=content, status_code=status)
|
||||
|
||||
if headers is not None:
|
||||
response.headers.update(headers)
|
||||
return response
|
||||
|
||||
+1
-1
@@ -1,3 +1,4 @@
|
||||
Babel
|
||||
click<8
|
||||
Flask
|
||||
pyproj
|
||||
@@ -8,4 +9,3 @@ rasterio
|
||||
shapely
|
||||
tinydb
|
||||
unicodecsv
|
||||
Babel
|
||||
|
||||
Reference in New Issue
Block a user