Publishing
Providers publish notifications to announce data or events. You need credentials with permission to publish to the event type. Publishing sends a notification, not the file it describes, and does not grant access to that file. Users who only want to receive notifications can go to Listening.
Discover what each event type requires
The server operator owns the schemas. The client can inspect them, but cannot
install one. These examples use the same small mars schema as the
quickstart. Your server may differ.
Start with pyaviso installed in your Python environment. Set
AVISO_BASE_URL and either AVISO_TOKEN or AVISO_USERNAME/AVISO_PASSWORD,
as in Set the environment.
pyaviso.Env() requires credentials and prefers the token when both are set.
For an anonymous server, pass auth=pyaviso.Anonymous() instead.
Save this as discover.py and run python discover.py. It lists event types
and prints the complete schema response for mars. If mars is absent, use a
listed name and run again:
import json
import os
import pyaviso
client = pyaviso.AvisoClient(
base_url=os.environ["AVISO_BASE_URL"], auth=pyaviso.Env()
)
print(client.schema().event_types)
print(json.dumps(client.schema_for("mars").as_dict(), indent=2, sort_keys=True))
With only this example schema installed, output looks like this:
['mars']
{
"event_type": "mars",
"schema": {
"identifier": {
"class": {
"required": true,
"type": "EnumHandler",
"values": [
"od",
"rd"
]
},
"step": {
"range": null,
"required": false,
"type": "IntHandler"
}
},
"payload": {
"required": false
}
},
"status": "success"
}
class must be od or rd; EnumHandler means a choice from a list. step
is a whole number (IntHandler), with no range limit here. Publishing needs
every identifier field, including step. The identifier’s required: false
means it may be omitted from a listener filter, not from a notification. The
payload is optional for this schema. Listing a schema does not prove you have
permission to publish to it.
A complete publish script
Save this as publish.py and run python publish.py in the same terminal. It
announces an operational forecast (class=od) at step 12:
import os
import pyaviso
client = pyaviso.AvisoClient(
base_url=os.environ["AVISO_BASE_URL"], auth=pyaviso.Env()
)
response = client.notify(
event_type="mars",
identifier={"class": "od", "step": 12},
payload={"location": "file:///data/forecast.grib"},
)
print(response.status)
On acceptance it prints:
success
notify takes keyword arguments: event_type names the schema, identifier
supplies its labels, and payload carries extra information. Pass Python
numbers and lists directly; 12 becomes a JSON number automatically. The
Python keyword is payload, not data.
The server normalizes scalar identifiers in received notifications: this
step comes back as the string "12". Payload numbers keep their JSON types.
The returned NotifyResponse has three string properties: status,
request_id, and processed_at. The request ID is for tracing the HTTP request
in server logs, not a notification ID or replay position. processed_at is the
server’s timestamp. Success means the server accepted the notification, not
that a user has received it or downloaded the file.
Publishing many notifications at once
To announce several datasets, put their notifications in a list and pass it to
notify_many. Here we announce forecasts for steps 24 and 48. In publish.py,
replace the response = client.notify(...) call and its print with this block,
keeping the imports and client initialization:
notifications = [
{
"event_type": "mars",
"identifier": {"class": "od", "step": 24},
"payload": {"location": "file:///data/forecast-24.grib"},
},
{
"event_type": "mars",
"identifier": {"class": "od", "step": 48},
"payload": {"location": "file:///data/forecast-48.grib"},
},
]
results = client.notify_many(notifications)
for result in results:
if result.response is not None:
print("Notification accepted")
else:
print("Notification failed:", result.error)
Each notification has its own result. Check for failures even if others succeeded. Results are returned in the same order as the input list.
Each input is a dict using the same keys as notify. The schema still decides
which fields must be supplied. Results are in input order; index starts at
zero. ok is a boolean. On success, response is a NotifyResponse and
error is None; on failure, response is None and error holds the
exception.
The batch is not atomic: valid notifications can be stored even if another
fails. Check the outcome before retrying. Malformed batch input, such as an
item missing event_type, raises ValueError before sending any requests.
A non-dict item raises TypeError.
The optional concurrency argument limits how many notifications are sent at
the same time. Omitting it or passing 0 allows up to 16 requests at a time.
The batch uses different step values from the single notification. On a
backend that keeps only the latest notification per subject, publishing the
same routing identifiers again replaces the retained record. Replay reads
retained history, not every publish ever sent.
Error paths
For the mars schema above, leaving out step is an error even though it is
optional in filters. This intentionally invalid example keeps the imports and
client initialization from publish.py:
try:
client.notify(event_type="mars", identifier={"class": "od"})
except pyaviso.HttpError as error:
print(f"server rejected: status={error.status} request_id={error.request_id}")
print(error.body)
The server rejects the missing identifier with HTTP 400. Use its error body to find the field to fix. An unknown event type, an unsupported identifier, or a value outside the schema’s choices can also be rejected by the server. The client does not decide which identifier names your server allows.
pyaviso.HttpErrormeans a non-2xx HTTP response.statusis an integer,bodyis a string, andrequest_idis a string orNonewhen unavailable.pyaviso.AuthErrormeans the auth source could not produce credentials.pyaviso.Env()can raise it during setup, beforenotifyis called.pyaviso.TransportErrorcovers connection or response-transfer failures. A failure does not prove the server received nothing.
Publishes are not automatically retried after transport failures: the server may already have stored the notification. Check before resending to avoid duplicates. After HTTP 401, a client with an auth provider attempts credential refresh and retries once. See Authentication for credential setup.
Payload shape
The mars payload is optional. To publish without one, keep the imports and
client initialization from publish.py and use:
response = client.notify(
event_type="mars", identifier={"class": "rd", "step": 60}
)
print(response.status)
payload accepts JSON-compatible Python values, including nested dicts and
lists, strings, numbers, and booleans. None means no payload in notify.
The server schema determines whether a payload is required. Put extra details
in the payload, rather than inventing identifier fields. Identifiers must
match their handlers; arbitrary objects are not valid values for the mars
enum and integer fields.
Identifier shapes
Spatial examples need a different schema. The following publish assumes the
operator has installed the public server
observations point-cloud schema.
It requires date (DateHandler, format %Y%m%d), point_cloud
(PointCloudHandler, at most 10,000 points), and a payload. Inspect it with
client.schema_for("observations") before using this example.
Keep the imports and client initialization from publish.py:
response = client.notify(
event_type="observations",
identifier={
"date": "20260601",
"point_cloud": [[46.0, 8.0], [47.0, 9.0]],
},
payload={"source": "stations"},
)
print(response.status)
A point is [latitude, longitude]. Point clouds are lists of those pairs and
accept arrays only. Pass Python lists, not json.dumps(...) strings. Clouds
do not need a closing repeat; duplicate points are allowed and keep their
order.
For this schema, subscribers filter with date and polygon, not
point_cloud. A polygon is a list of at least four coordinate pairs, with the
first pair repeated last. Any cloud point inside or on the boundary matches.
See spatial filters.
The reserved point field is a watch/replay filter for polygon streams, not a
provider identifier. The
alternative coordinate format
applies to points and polygons, not clouds.
Admin operations
Operators can use wipe_stream(event_type) or wipe_all() to clear retained
notifications, and delete_notification(notification_id) to remove one by ID.
These are destructive operations requiring the appropriate permissions. A
publish response’s request_id is not the notification ID to delete. See
the API reference for the method signatures.
Async equivalent
If your application uses asyncio, the async client takes the same arguments:
import asyncio
import os
import pyaviso
async def main() -> None:
client = pyaviso.AsyncAvisoClient(
base_url=os.environ["AVISO_BASE_URL"], auth=pyaviso.Env()
)
response = await client.notify(
event_type="mars",
identifier={"class": "rd", "step": 72},
payload={"location": "file:///data/forecast-72.grib"},
)
print(response.status)
asyncio.run(main())
This uses the same mars schema and prints success on acceptance. See
Async for concurrent publishing and listening.