Skip to content

Python Quickstart ​

Use server-side: tile proxying, batch geocoding, signed-URL signing.

For client-side maps in a Python web app (Streamlit, Dash, Flask templates), serve our style URL straight to MapLibre in the browser.

Install ​

bash
pip install requests

Tile proxy (Flask) ​

python
import os
import time
import hmac
import hashlib
from flask import Flask, request, Response
import requests

app = Flask(__name__)
KEY = os.environ['MFD_SRV_KEY']
SECRET = os.environ['MFD_SRV_SECRET']
TILE_BASE = 'https://tiles.mapsfordevs.com'

def sign(method: str, path: str) -> dict:
    ts = str(int(time.time()))
    sig = hmac.new(SECRET.encode(), f'{method}\n{path}\n{ts}'.encode(), hashlib.sha256).hexdigest()
    return {'Authorization': f'Bearer {KEY}', 'X-MFD-Timestamp': ts, 'X-MFD-Signature': sig}

@app.route('/tiles/<int:z>/<int:x>/<int:y>.pbf')
def tile(z, x, y):
    path = f'/tiles/{z}/{x}/{y}.pbf'
    upstream = requests.get(f'{TILE_BASE}{path}', headers=sign('GET', path), stream=True)
    return Response(
        upstream.iter_content(8192),
        status=upstream.status_code,
        content_type='application/x-protobuf',
        headers={'Cache-Control': 'public, max-age=86400'}
    )

Batch geocoding ​

python
import requests

KEY = 'mfd_pub_xxx'

def geocode(query: str, country: str = None, limit: int = 5):
    params = {'q': query, 'limit': limit}
    if country:
        params['country'] = country
    r = requests.get(
        'https://api.mapsfordevs.com/geocode/forward',
        headers={'Authorization': f'Bearer {KEY}'},
        params=params,
        timeout=10
    )
    body = r.json()
    if not body.get('ok'):
        raise RuntimeError(body.get('error'))
    return body['items']

results = geocode('80 Sturdee Ave Rosebank', country='za')
for hit in results:
    print(hit['lat'], hit['lng'], hit['label'])

Retry with backoff ​

python
import time
import requests
from requests.exceptions import RequestException

def fetch_with_retry(url, headers=None, attempts=4):
    for i in range(attempts):
        try:
            r = requests.get(url, headers=headers, timeout=10)
            if r.status_code < 500 and r.status_code != 429:
                return r
            delay = int(r.headers.get('Retry-After', 0)) or (2 ** i)
        except RequestException:
            delay = 2 ** i
        time.sleep(delay)
    raise RuntimeError(f'failed after {attempts} attempts')