Skip to main content

odatadol

Read an OData v4 entity set as a Python Mapping, with the standard library only (urllib, json, xml.etree): zero runtime dependencies.

pip install odatadol
from odatadol import ODataReader, BasicAuth

store = ODataReader(
    'https://example.com/odata/v4/Shop', 'Products',
    key='ProductID', credentials=BasicAuth('reader', password),
)
len(store)          # one $count request
store['P0042']      # one filtered request
list(store)         # the key column only, in key order
dict(store.items()) # every record, read in bulk

It is read-only by construction: every request is a GET, and no function takes an HTTP verb.

The functions under the facade

ODataReader is a thin layer over plain functions, each usable on its own. They take the service root and the entity set name, then keyword-only options.

function does
entity_count(service, entity, filter=) $count, sent with Accept: */*
entity_records(service, entity, select=, filter=, orderby=, top=, page_size=) records, lazily, paged by $top/$skip or @odata.nextLink, annotations dropped; a read ends on an empty page, so a server that silently caps its pages loses nothing
entity_columns(service, entity, select=, key=, version=, filter=, max_url=) a $select too wide for one URL, read as column groups and joined on key
entity_metadata(service, entity) $metadata as {name: {'type': 'Edm.…', 'nullable': bool, 'annotations': {...}}}
odata_get(url) the one request everything goes through
from decimal import Decimal
from odatadol import entity_records, entity_metadata

entity_metadata(service, 'Products')['Price']
# {'type': 'Edm.Decimal', 'nullable': True}

newest = next(entity_records(
    service, 'Products', select=['ProductID', 'ModifiedAt'],
    filter='ModifiedAt ne null', orderby='ModifiedAt desc', top=1,
    parse_float=Decimal,   # keep every digit of a fractional number
))

Seams

Every function takes the same keyword arguments for what a host may need to swap:

  • fetch=(url, headers) -> (status, body bytes): the transport. Defaults to urllib_fetch (use functools.partial(urllib_fetch, timeout=...) to change its timeout). Tests pass odatadol.testing.FakeService, a scripted server that records every URL it was asked for.
  • credentials=: None (anonymous), BasicAuth(account, password), BearerToken(token), anything with a header() -> str method, or a zero-argument callable returning one of those, so a keychain or file lookup happens only when a request is made. Neither the password nor the Authorization header ever appears in an error, and credentials never follow a redirect or a @odata.nextLink to another origin (a cross-origin nextLink is refused). A server that would page forever (ignoring $skip, or repeating its nextLink) is stopped with an ODataError.
  • max_url= (default 2000) and page_size= (default 1000).
  • parse_float=: applied to every fractional JSON number; decimal.Decimal keeps every digit (default float).
  • literal= (on ODataReader): writes a key as a $filter literal for store[k] and k in store; defaults to odata_literal (str, numbers, bool, uuid.UUID, datetime objects). For an Edm.Guid key that iteration returns as text, pass literal=str.

Wide reads are one moment, or they raise

A front end such as IIS refuses URLs past about 2,048 characters, answering its own 404 as if the entity did not exist. A $select of a hundred or more fields is past that, so entity_columns (and ODataReader with a wide select=) splits it into groups that fit max_url, reads each in key order, and joins them on the key.

A join of separate reads is only a true record if every read saw the same records in the same state, so:

  • version= names a property the server rewrites on every edit (a modification timestamp). It is read in every group and must agree per key; otherwise an edit landing between two reads would yield a record that never existed.
  • With a single group, a narrow key/version read follows it, because $skip paging over a delete before the cursor and an insert after it skips a live record while the count still balances.
  • An empty or repeated key is refused (ODataError): joining on it would merge different records.
  • Reads that disagree raise UnstableRead (an ODataError), saying how many records were new, gone or edited. The entity set changed during the read; retrying is safe.

Server quirks it copes with

Each was met against a real server and has a test:

  • $count with Accept: text/plain refused; odatadol sends */*.
  • Property names with spaces must be double-quoted in $select/$orderby (quote_name), as FileMaker's OData requires.
  • A percent-encoded : inside a $filter timestamp is a syntax error; : travels bare, as RFC 3986 allows.
  • Raw control characters (a bare CR) inside JSON strings; parsed with json.loads(strict=False) and kept verbatim.

Writes

Not here, and not behind a flag on the reader. If writing (POST/PATCH) is ever added it will be a separate, opt-in class with its own transport seam (method, body and response headers, for ETags), so code holding a reader cannot reach a write by passing the wrong argument, and the reader's fetch=(url, headers) -> (status, bytes) stays as it is. A fetch may do its own retries (on 429 or 503, say); the reader needs nothing more from it.

Metadata

Release files for odatadol 0.0.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for odatadol 0.0.2
File Size Uploaded Provenance Actions
odatadol-0.0.2.tar.gz 30.1 kB Without provenance Details for odatadol-0.0.2.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for odatadol 0.0.2
File Interpreter ABI Platform Actions
odatadol-0.0.2-py3-none-any.whl Python 3 none any Details for odatadol-0.0.2-py3-none-any.whl

Total release size: 57.1 kB

Release files / odatadol-0.0.2.tar.gz

Download URL odatadol-0.0.2.tar.gz
Size 30.1 kB
Tags Source
SHA-256 checksum
How to use checksums
b323f7fa1cb393cb21deeb6b0c74c4576cef7479c38a278d388f1659cf9e23f0
BLAKE2b-256 checksum
How to use checksums
6186269ad0b3eb7f9b5baa377c9b723ffefc32a1af1e0bbff623b3c32fd798bd
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.13.6 {"installer":{"name":"uv","version":"0.13.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / odatadol-0.0.2-py3-none-any.whl

Download URL odatadol-0.0.2-py3-none-any.whl
Size 27.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
47c51a1566c81028854db447798a13eca757e3b6fefb8f214bb0aeb10f17ae47
BLAKE2b-256 checksum
How to use checksums
619f1d00a39fd5a770cca3063bc2792af6675566dba367729073f9836e0e454e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.13.6 {"installer":{"name":"uv","version":"0.13.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Security for odatadol 0.0.2

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.0.2 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page