Writing plugins#
Since MapProxy 1.15, it is possible to write plugins for MapProxy that can add new sources, services or commands. This requires Python >= 3.7
Example#
The mapproxy_hips plugin at rouault/mapproxy_hips is an example of a plugin, which adds a new source, service and customizes the demo service, demonstrating all the below points.
How to add a plugin ?#
A plugin should be written as a Python package whose setuptools setup()
method has a entry_points
keyword with a group mapproxy
pointing to
a module with a plugin_entrypoint
method.
entry_points={"mapproxy": ["hips = mapproxy_hips.pluginmodule"]},
In this example, the mapproxy_hips/pluginmodule.py
file should have
a plugin_entrypoint
method taking no argument and returning nothing.
def plugin_entrypoint():
# call different registration methods, like register_service_configuration(),
# register_source_configuration()
pass
That method is in charge of registering the various registration methods detailed hereafter.
Plugins will often by dependent on MapProxy internal classes. It is their responsibility to check the MapProxy version, in case the MapProxy internal API or behavior would change and make them incompatible.
Adding a new service#
The mapproxy.config.loader
module has a register_service_configuration()
method to register a new service and specify the allowed keywords for it in
the YAML configuration file.
def register_service_configuration(service_name, service_creator,
yaml_spec_service_name = None, yaml_spec_service_def = None):
""" Method used by plugins to register a new service.
:param config_name: Name of the service
:type config_name: str
:param service_creator: Creator method of the service
:type service_creator: method of type (serviceConfiguration: ServiceConfiguration, conf: dict) -> Server
:param yaml_spec_service_name: Name of the service in the YAML configuration file
:type yaml_spec_service_name: str
:param yaml_spec_service_def: Definition of the service in the YAML configuration file
:type yaml_spec_service_def: dict
"""
This can for example by used like the following snippet:
from mapproxy.config.loader import register_service_configuration
from mapproxy.service.base import Server
class MyExtraServiceServer(Server):
# Look at classes at https://github.com/mapproxy/mapproxy/tree/master/mapproxy/service
# for a real implementation
names = ('my_extra_service',)
def __init__(self):
pass
def my_extra_service_method(serviceConfiguration, conf):
return MyExtraServiceServer()
register_service_configuration('my_extra_service', my_extra_service_method,
'my_extra_service', {'foo': str()})
This allows the following declaration in the YAML mapproxy configuration file:
services:
my_extra_service:
foo: bar
A real-world implementation can be found at rouault/mapproxy_hips
Customizing layer metadata in YAML configuration file#
When implementing a new service, it might be useful to add per-layer metadata
for it. The YAML validator needs to be updated to recognize the new keywords.
The add_subcategory_to_layer_md()
method of the mapproxy.config.spec
module
can be used to do that.
def add_subcategory_to_layer_md(category_name, category_def):
""" Add a new category to wms_130_layer_md.
Used by plugins
"""
This can for example be used like in the following snippet:
from mapproxy.config.spec import add_subcategory_to_layer_md
# Add a 'hips' subcategory to layer spec to be able to define hips service
# specific layer metadata
add_subcategory_to_layer_md('hips', anything())
Adding a new source#
The mapproxy.config.loader
module has a register_source_configuration()
method to register a new source and specify the allowed keywords for it in
the YAML configuration file.
def register_source_configuration(config_name, config_class,
yaml_spec_source_name = None, yaml_spec_source_def = None):
""" Method used by plugins to register a new source configuration.
:param config_name: Name of the source configuration
:type config_name: str
:param config_class: Class of the source configuration
:type config_name: SourceConfiguration
:param yaml_spec_source_name: Name of the source in the YAML configuration file
:type yaml_spec_source_name: str
:param yaml_spec_source_def: Definition of the source in the YAML configuration file
:type yaml_spec_source_def: dict
"""
This can for example by used like the following snippet:
from mapproxy.config.loader import register_source_configuration
from mapproxy.config.loader import SourceConfiguration
class my_source_configuration(SourceConfiguration):
source_type = ('my_extra_source',)
def source(self, params=None):
# Look at classes at https://github.com/mapproxy/mapproxy/tree/master/mapproxy/source
# for a real implementation
class MySource(object):
def __init__(self):
self.extent = None
return MySource()
register_source_configuration('my_extra_source', my_source_configuration,
'my_extra_source', {'foo': str()})
This allows the following declaration in the YAML mapproxy configuration file:
sources:
some_source_name:
type: my_extra_source
foo: bar
A real-world implementation can be found at rouault/mapproxy_hips
Customizing the demo service#
The MapProxy Demo Service can be customized in two ways:
Customizing the output of the
/demo
HTML output, typically by adding entries for new services. This is done with theregister_extra_demo_substitution_handler()
method of themapproxy.service.demo
module.def register_extra_demo_substitution_handler(handler): """ Method used by plugins to register a new handler for doing substitutions to the HTML template used by the demo service. The handler passed to this method is invoked by the DemoServer._render_template() method. The handler may modify the passed substitutions dictionary argument. Keys of particular interest are 'extra_services_html_beginning' and 'extra_services_html_end' to add HTML content before/after built-in services. :param handler: New handler for incoming requests :type handler: function that takes 3 arguments(DemoServer instance, req and a substitutions dictionary argument). """
Handling new request paths under the
/demo/
hierarchy, typically to implement a new service. This is done with theregister_extra_demo_server_handler()
method of themapproxy.service.demo
module.def register_extra_demo_server_handler(handler): """ Method used by plugins to register a new handler for the demo service. The handler passed to this method is invoked by the DemoServer.handle() method when receiving an incoming request. This enables handlers to process it, in case it is relevant to it. :param handler: New handler for incoming requests :type handler: function that takes 2 arguments (DemoServer instance and req) and returns a string with HTML content or None """
This can for example be used like in the following snippet:
from mapproxy.service.demo import register_extra_demo_server_handler, register_extra_demo_substitution_handler
def demo_server_handler(demo_server, req):
if 'my_service' in req.args:
return 'my_return'
return None
def demo_substitution_handler(demo_server, req, substitutions):
html = '<h2>My extra service</h2>'
html += '<a href="/demo?my_service">My service</a>'
substitutions['extra_services_html_beginning'] += html
register_extra_demo_server_handler(demo_server_handler)
register_extra_demo_substitution_handler(demo_substitution_handler)
A real-world example can be found at rouault/mapproxy_hips
Adding new commands to mapproxy-util#
New commands can be added to mapproxy-util by using the
register_command()
method of the mapproxy.script.util
module
def register_command(command_name, command_spec):
""" Method used by plugins to register a command.
:param command_name: Name of the command
:type command_name: str
:param command_spec: Definition of the command. Dictionary with a 'func' and 'help' member
:type command_spec: dict
"""
This can for example be used like in the following snippet:
import optparse
from mapproxy.script.util import register_command
def my_command(args=None):
parser = optparse.OptionParser("%prog my_command [options] -f mapproxy_conf -l layer")
parser.add_option("-f", "--mapproxy-conf", dest="mapproxy_conf",
help="MapProxy configuration.")
parser.add_option("-l", "--layer", dest="layer", help="Layer")
if args:
args = args[1:] # remove script name
(options, args) = parser.parse_args(args)
if not options.mapproxy_conf or not options.layer:
parser.print_help()
sys.exit(1)
# Do something
register_command('my_command', {
'func': my_command,
'help': 'Do something.'
})
A real-world example can be found at rouault/mapproxy_hips
Intercepting request#
It is possible to intercept any request in a plugin with register_request_interceptor. The provided function will be called on any request and should always return a request, either the original one or a new one.
Example:
from mapproxy.wsgiapp import register_request_interceptor
def interceptor(req):
if req.path.startswith('service'):
environ = req.environ.copy()
environ['QUERY_STRING'] = environ['QUERY_STRING'].replace('foo', 'bar')
return Request(environ)
return req
register_request_interceptor(interceptor)
A real world example can be found at mapproxy/wmts-rest-legend-plugin
Credits#
The development of the plugin mechanism has been funded by Centre National d’Etudes Spatiales (CNES): https://cnes.fr