Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion pygeoapi/api/maps.py
Original file line number Diff line number Diff line change
Expand Up @@ -385,7 +385,6 @@ def get_oas_30(cfg: dict, locale: str) -> tuple[list[dict[str, str]], dict[str,
'operationId': 'getMap',
'parameters': [
{'$ref': '#/components/parameters/bbox'},
{'$ref': f"{OPENAPI_YAML['oapif-1']}#/components/parameters/datetime"}, # noqa
{'$ref': f"{OPENAPI_YAML['oamaps']}#/components/parameters/subset"}, # noqa
{
'name': 'width',
Expand Down
58 changes: 36 additions & 22 deletions pygeoapi/openapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,12 +134,13 @@ def gen_response_object(description: str, media_type: str,
return response


def gen_contact(cfg: dict) -> dict:
def gen_contact(cfg: dict, locale_: str) -> dict:
"""
Generates an OpenAPI contact object with OGC extensions
based on OGC API - Records contact

:param cfg: `dict` of configuration
:param locale_: `str` of locale

:returns: `dict` of OpenAPI contact object
"""
Expand All @@ -148,40 +149,47 @@ def gen_contact(cfg: dict) -> dict:
has_phones = False

contact = {
'name': cfg['metadata']['provider']['name']
'name': l10n.translate(cfg['metadata']['provider']['name'], locale_)
}

for key in ['url', 'email']:
if key in cfg['metadata']['provider']:
contact[key] = cfg['metadata']['provider'][key]
contact[key] = l10n.translate(cfg['metadata']['provider'][key],
locale_)

contact['x-ogc-serviceContact'] = {
'name': cfg['metadata']['contact']['name'],
'name': l10n.translate(cfg['metadata']['contact']['name'], locale_),
'addresses': []
}

if 'position' in cfg['metadata']['contact']:
contact['x-ogc-serviceContact']['position'] = cfg['metadata']['contact']['position'] # noqa
contact['x-ogc-serviceContact']['position'] = l10n.translate(
cfg['metadata']['contact']['position'], locale_)

if any(address in ['address', 'city', 'stateorprovince', 'postalcode', 'country'] for address in cfg['metadata']['contact']): # noqa
has_addresses = True

if has_addresses:
address = {}
if 'address' in cfg['metadata']['contact']:
address['deliveryPoint'] = [cfg['metadata']['contact']['address']]
address['deliveryPoint'] = [l10n.translate(
cfg['metadata']['contact']['address'], locale_)]

if 'city' in cfg['metadata']['contact']:
address['city'] = cfg['metadata']['contact']['city']
address['city'] = l10n.translate(
cfg['metadata']['contact']['city'], locale_)

if 'stateorprovince' in cfg['metadata']['contact']:
address['administrativeArea'] = cfg['metadata']['contact']['stateorprovince'] # noqa
address['administrativeArea'] = l10n.translate(
cfg['metadata']['contact']['stateorprovince'], locale_)

if 'postalCode' in cfg['metadata']['contact']:
address['administrativeArea'] = cfg['metadata']['contact']['postalCode'] # noqa
address['administrativeArea'] = l10n.translate(
cfg['metadata']['contact']['postalCode'], locale_)

if 'country' in cfg['metadata']['contact']:
address['administrativeArea'] = cfg['metadata']['contact']['country'] # noqa
address['administrativeArea'] = l10n.translate(
cfg['metadata']['contact']['country'], locale_)

contact['x-ogc-serviceContact']['addresses'].append(address)

Expand All @@ -192,33 +200,39 @@ def gen_contact(cfg: dict) -> dict:
if has_phones:
if 'phone' in cfg['metadata']['contact']:
contact['x-ogc-serviceContact']['phones'].append({
'type': 'main', 'value': cfg['metadata']['contact']['phone']
'type': 'main', 'value': l10n.translate(
cfg['metadata']['contact']['phone'], locale_)
})

if 'fax' in cfg['metadata']['contact']:
contact['x-ogc-serviceContact']['phones'].append({
'type': 'fax', 'value': cfg['metadata']['contact']['fax']
'type': 'fax', 'value': l10n.translate(
cfg['metadata']['contact']['fax'], locale_)
})

if 'email' in cfg['metadata']['contact']:
contact['x-ogc-serviceContact']['emails'] = [{
'value': cfg['metadata']['contact']['email']
'value': l10n.translate(
cfg['metadata']['contact']['email'], locale_)
}]

if 'url' in cfg['metadata']['contact']:
contact['x-ogc-serviceContact']['links'] = [{
'type': 'text/html',
'href': cfg['metadata']['contact']['url']
'href': l10n.translate(cfg['metadata']['contact']['url'], locale_)
}]

if 'instructions' in cfg['metadata']['contact']:
contact['x-ogc-serviceContact']['contactInstructions'] = cfg['metadata']['contact']['instructions'] # noqa
contact['x-ogc-serviceContact']['contactInstructions'] = l10n.translate( # noqa
cfg['metadata']['contact']['instructions'], locale_)

if 'hours' in cfg['metadata']['contact']:
contact['x-ogc-serviceContact']['hoursOfService'] = cfg['metadata']['contact']['hours'] # noqa
contact['x-ogc-serviceContact']['hoursOfService'] = l10n.translate(
cfg['metadata']['contact']['hours'], locale_)

if 'role' in cfg['metadata']['contact']:
contact['x-ogc-serviceContact']['hoursOfService'] = cfg['metadata']['contact']['role'] # noqa
contact['x-ogc-serviceContact']['hoursOfService'] = l10n.translate(
cfg['metadata']['contact']['role'], locale_)

return contact

Expand Down Expand Up @@ -255,11 +269,11 @@ def get_oas_30(cfg: dict, fail_on_invalid_collection: bool = True) -> dict:
'description': l10n.translate(cfg['metadata']['identification']['description'], locale_), # noqa
'x-keywords': l10n.translate(cfg['metadata']['identification']['keywords'], locale_), # noqa
'termsOfService':
cfg['metadata']['identification']['terms_of_service'],
'contact': gen_contact(cfg),
l10n.translate(cfg['metadata']['identification']['terms_of_service'], locale_), # noqa
'contact': gen_contact(cfg, locale_),
'license': {
'name': cfg['metadata']['license']['name'],
'url': cfg['metadata']['license']['url']
'name': l10n.translate(cfg['metadata']['license']['name'], locale_), # noqa
'url': l10n.translate(cfg['metadata']['license']['url'], locale_)
},
'version': api_rules.api_version
}
Expand Down Expand Up @@ -360,7 +374,7 @@ def get_oas_30(cfg: dict, fail_on_invalid_collection: bool = True) -> dict:
'description': l10n.translate(cfg['metadata']['identification']['description'], locale_), # noqa
'externalDocs': {
'description': 'information',
'url': cfg['metadata']['identification']['url']}
'url': l10n.translate(cfg['metadata']['identification']['url'], locale_)} # noqa
}
)

Expand Down
16 changes: 15 additions & 1 deletion tests/other/test_openapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,20 @@ def config_hidden_resources():
return yaml_load(fh)


@pytest.fixture()
def config_i18n():
filename = 'pygeoapi-test-config-i18n.yml'
with open(get_test_file_path(filename)) as fh:
return yaml_load(fh)


@pytest.fixture()
def openapi():
with open(get_test_file_path('pygeoapi-test-openapi.yml')) as fh:
return yaml_load(fh)


def test_str2bool():
def test_get_ogc_schemas_location():

default = {
'url': 'http://localhost:5000'
Expand Down Expand Up @@ -141,6 +148,13 @@ def test_hidden_resources(config_hidden_resources):
assert '/collections/obs/items' not in openapi_doc['paths']


def test_i18n(config_i18n):
openapi_doc = get_oas(config_i18n)

assert isinstance(openapi_doc['info']['contact']['name'], str)
assert openapi_doc['info']['contact']['name'] == 'Organization Name'


def test_admin_empty_resources(config_admin_empty_resources):
openapi_doc = get_oas(config_admin_empty_resources)
assert '/admin/config' in openapi_doc['paths']
192 changes: 192 additions & 0 deletions tests/pygeoapi-test-config-i18n.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
# =================================================================
#
# Authors: Tom Kralidis <tomkralidis@gmail.com>
#
# Copyright (c) 2026 Tom Kralidis
#
# 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.
#
# =================================================================

server:
bind:
host: 0.0.0.0
port: 5000
url: http://localhost:5000/
mimetype: application/json; charset=UTF-8
encoding: utf-8
gzip: false
languages:
# First language is the default language
- en-US
- fr-CA
cors: true
pretty_print: true
limits:
default_items: 10
max_items: 10
# templates: /path/to/templates
map:
url: https://tile.openstreetmap.org/{z}/{x}/{y}.png
attribution: '&copy; <a href="https://openstreetmap.org/copyright">OpenStreetMap contributors</a>'
manager:
name: TinyDB
connection: /tmp/pygeoapi-test-process-manager.db
output_dir: /tmp

logging:
level: DEBUG
#logfile: /tmp/pygeoapi.log

metadata:
identification:
title:
en: pygeoapi default instance
fr: instance par défaut de pygeoapi
description:
en: pygeoapi provides an API to geospatial data
fr: pygeoapi fournit une API aux données géospatiales
keywords:
en:
- geospatial
- data
- api
fr:
- géospatiale
- données
- api
keywords_type: theme
terms_of_service: https://creativecommons.org/licenses/by/4.0/
url: http://example.org
license:
name:
en: CC-BY 4.0 license
fr: license CC-BY 4.0 license
url:
en: https://creativecommons.org/licenses/by/4.0/
fr: https://creativecommons.org/licenses/by/4.0/
provider:
name:
en: Organization Name
fr: nom d'organisation
url:
en: https://pygeoapi.io
fr: https://pygeoapi.io
contact:
name:
en: Lastname, Firstname
fr: nom de famille, nom
position:
en: Position Title
fr: titre du poste
address:
en: Mailing Address
fr: address postale
city:
en: City
fr: ville
stateorprovince:
en: Administrative Area
fr: zone administrative
postalcode:
en: Zip or Postal Code
fr: code postale
country:
en: Country
fr: pays
phone:
en: +xx-xxx-xxx-xxxx
fr: +xx-xxx-xxx-xxxx
fax:
en: +xx-xxx-xxx-xxxx
fr: +xx-xxx-xxx-xxxx
email: you@example.org
url:
en: Contact URL
fr: URL de contact
hours:
en: Hours of Service
fr: heures de service
instructions:
en: During hours of service. Off on weekends.
fr: pendant les heures de service. Fermé le week-end.
role: pointOfContact

resources:
obs:
type: collection
title:
en: Observations
fr: Observations
description:
en: My cool observations
fr: Mes belles observations
keywords:
- observations
- monitoring
links:
- type: text/csv
rel: canonical
title: data
href: https://github.com/mapserver/mapserver/blob/branch-7-0/msautotest/wxs/data/obs.csv
hreflang: en-US
- type: text/csv
rel: alternate
title: data
href: https://raw.githubusercontent.com/mapserver/mapserver/branch-7-0/msautotest/wxs/data/obs.csv
hreflang: en-US
linked-data:
context:
- schema: https://schema.org/
stn_id:
"@id": schema:identifier
"@type": schema:Text
datetime:
"@type": schema:DateTime
"@id": schema:observationDate
value:
"@type": schema:Number
"@id": schema:QuantitativeValue
extents:
spatial:
bbox: [-180,-90,180,90]
crs: http://www.opengis.net/def/crs/OGC/1.3/CRS84
temporal:
begin: 2000-10-30T18:24:39Z
end: 2007-10-30T08:57:29Z
trs: http://www.opengis.net/def/uom/ISO-8601/0/Gregorian
resolution: P1D
default: 2000-10-30T18:24:39Z
providers:
- type: feature
name: CSV
data: tests/data/obs.csv
crs:
- http://www.opengis.net/def/crs/OGC/1.3/CRS84
- http://www.opengis.net/def/crs/EPSG/0/4326
- http://www.opengis.net/def/crs/EPSG/0/3857
- http://www.opengis.net/def/crs/EPSG/0/28992
storage_crs: http://www.opengis.net/def/crs/OGC/1.3/CRS84
id_field: id
geometry:
x_field: long
y_field: lat
Loading