The code QSO Graph's Python packages share, in one place, so it is written and tested once. Standard library only: no dependencies.
| Module | What |
|---|---|
qso_graph_lib.adif |
ADIF's ADI format, read and written as ADIF 3.1.7 defines it. Passes ADIF's own 3.1.7 test files; non-compliant input real logs contain is repaired where it can be and reported in Document.problems, never silently |
qso_graph_lib.jt.protocol |
WSJT-X's UDP messages, encoded and decoded: Heartbeat, Status, Decode, WSPRDecode, Clear, Close, Replay and the rest, schema 3, written to WSJT-X's own Network/NetworkMessage.hpp |
qso_graph_lib.jt.alltxt |
WSJT-X's ALL.TXT, the log of every decode, read into cycles |
The JT modules are used by qso-graph-sim and jt-mcp; the ADIF module by the logbook servers and qsp-client.
uv add qso-graph-libIt has no command of its own; it is imported.
from qso_graph_lib.jt import protocol
message = protocol.parse(datagram) # ValueError if it isn't a WSJT-X message
if message.kind == protocol.MessageType.DECODE:
print(message.sender, message.fields["snr"], message.fields["message"])Unknown message types, and fields past the ones it knows, are ignored, as WSJT-X's protocol requires. A message cut short inside a field it needs is refused.
from qso_graph_lib import adif
doc = adif.read_file("log.adi") # line ends as written, never text mode
for record in doc.records:
print(record.fields["CALL"], record.fields.get("BAND"))
for problem in doc.problems: # what broke ADIF's rules, and what was done
print(problem.offset, problem.message)
text = adif.write(records, header={"ADIF_VER": "3.1.7", "PROGRAMID": "my-app"})git clone https://github.com/qso-graph/qso-graph-lib.git
cd qso-graph-lib
uv sync --group dev
uv run pytestThis package is part of qso-graph, tools for amateur radio. See qso-graph.io for documentation.