Examples#

The examples below use PyMetKit’s public API and are executed as sybil tests. The building and accessing examples are self-contained; the expanding, equality, merging and parsing examples require the MARS language definitions shipped with metkit (see Installation).

Building a request#

A MarsRequest is a verb plus a selection of parameters passed as a plain mapping. Values may be scalars, numbers, collections, or /-separated strings.

from pymetkit import MarsRequest
request = MarsRequest("retrieve", {"class": "od", "param": [151, 129]})
assert request.verb() == "retrieve"
assert request["class"] == "od"
assert request["param"] == ['151', '129']

Numbers, ranges and /-separated strings are normalized to lists of strings:

request = MarsRequest("retrieve", {"step": range(0, 13, 6), "date": "20200101/20200102"})
assert request["step"] == ["0", "6", "12"]
assert request["date"] == ["20200101", "20200102"]

Accessing values#

A request behaves like a read/write mapping. A parameter with a single value returns a scalar, one with several values returns a list:

request = MarsRequest("retrieve", {"class": "od", "param": [151, 129]})
assert "class" in request
assert sorted(request.keys()) == ["class", "param"]
assert request.num_values("param") == 2
request["expver"] = "0001"
assert request["expver"] == "0001"

Iterating over a request yields (name, value) pairs, mirroring a plain dict:

request = MarsRequest("retrieve", {"class": "od", "param": [151, 129]})
assert list(request) == [("class", "od"), ("param", ["151", "129"])]

Pass a request to dict() to get a plain Python mapping, useful for serialisation or inspection:

request = MarsRequest("retrieve", {"class": "od", "param": [151, 129]})
assert dict(request) == {"class": "od", "param": ["151", "129"]}

Manipulating a request#

Parameters can be added or overwritten after construction. All value forms accepted at construction time are also valid for assignment — scalars, integers, ranges, lists and /-separated strings are all normalised the same way:

from pymetkit import MarsRequest

request = MarsRequest("retrieve", {"class": "od", "expver": "0001"})

request["date"] = "20230101/20230102"  # slash-separated → list
request["step"] = range(0, 13, 6)      # range → list
request["param"] = 130                 # integer → scalar

assert request["date"] == ["20230101", "20230102"]
assert request["step"] == ["0", "6", "12"]
assert request["param"] == "130"

request["step"] = [0, 6]              # overwrite with a shorter list
assert request["step"] == ["0", "6"]

A new request can be derived from an existing one by snapshotting it with dict(), adjusting selected values, and constructing a fresh request:

from pymetkit import MarsRequest

base = MarsRequest("retrieve", {"class": "od", "date": "-1", "param": "130", "step": "0"})
derived = MarsRequest(base.verb(), {**dict(base), "date": "20230101"})

assert derived["date"] == "20230101"
assert derived["param"] == "130"

Use in to guard access to parameters that may not be present:

from pymetkit import MarsRequest

request = MarsRequest("retrieve", {"class": "od", "param": "130"})
step = request["step"] if "step" in request else "0"
assert step == "0"

Equality#

Two requests are equal when they expand to the same result. MARS language aliases are resolved, so "od" and "operations" compare equal. Equality requires the MARS language definitions to be present.

from pymetkit import MarsRequest

r1 = MarsRequest("retrieve", {"class": "od",         "date": "20230101", "param": "130"})
r2 = MarsRequest("retrieve", {"class": "operations", "date": "20230101", "param": "130"})
r3 = MarsRequest("retrieve", {"class": "od",         "date": "20230101", "param": "131"})

assert r1 == r2   # "od" and "operations" are aliases
assert r1 != r3

Expanding and validating#

expand() returns one or more requests expanded against the MARS language definition. validate() checks a request without inheriting defaults and raises MetKitException on invalid input.

from pymetkit import MarsRequest, expand

request = MarsRequest(
    "retrieve",
    {
        "class": "od",
        "domain": "g",
        "date": "-1",
        "expver": "0001",
        "step": range(0, 13, 6),
    },
)

expanded = expand(request)           # inherit=True: fills in default values
assert expanded.verb() == "retrieve"

request.validate()                   # raises MetKitException if invalid

When expanding multiple requests, pass them as a list. Internal language checks are performed once for the whole batch rather than once per request:

from pymetkit import MarsRequest, expand

requests = [
    MarsRequest("retrieve", {"class": "od", "date": "-1", "param": str(p)})
    for p in [129, 130, 131]
]
expanded = expand(requests)
assert len(expanded) == 3

Merging requests#

merge() combines the values of two requests that carry the same parameters, keeping self’s values first and appending only the values of other that are not already present:

from pymetkit import MarsRequest

left = MarsRequest("retrieve", {"class": "od", "date": "-1", "levtype": "sfc"})
right = MarsRequest("retrieve", {"class": "od", "date": "20230101", "levtype": "sfc"})

merged = left.merge(right)
assert merged["date"] == ["-1", "20230101"]

Splitting requests#

split() decomposes a request into one request per value combination across the listed keys. All other parameters are carried over unchanged. The operation is purely structural and does not require the MARS language definitions.

from pymetkit import MarsRequest

request = MarsRequest(
    "retrieve",
    {"class": "od", "step": range(0, 13, 6), "param": [129, 130]},
)
parts = request.split(["step", "param"])
assert len(parts) == 6  # 3 steps × 2 params
assert parts[0]["step"] == "0"
assert parts[0]["param"] == "129"

Parsing requests#

parse_mars_request() parses one or more requests from a string or a file-like object:

from pymetkit import parse_mars_request

requests = parse_mars_request("retrieve,class=od,date=-1,param=129,step=12")
assert len(requests) == 1
assert requests[0].verb() == "retrieve"