model: escape single quotes in Edm.String literals - #313
Merged
filak-sap merged 1 commit intoAug 11, 2026
Conversation
EdmStringTypTraits.to_literal wrapped the value in single quotes without
escaping quotes contained in the value itself. An embedded single quote in
an OData string literal has to be doubled, so any filter on a value
containing an apostrophe produced a malformed query that services reject.
Against a live OData v2 service (ws.parlament.ch), filtering on the French
term "delit d'initie" yielded:
$filter=Language eq 'FR' and Title eq 'delit d'initie'
HTTP 400 - Syntax error at position 45
The literal terminates early at the apostrophe and the remainder is parsed
as syntax. With the quote doubled the same query returns HTTP 200.
from_literal is updated symmetrically to collapse doubled quotes, so that
to_literal/from_literal round-trip. Values with no quotes are unaffected in
both directions.
Note: to_literal now calls str(value) explicitly because the interpolation
that stringified the value implicitly is replaced by a .replace() call. This
does not change which types are accepted.
filak-sap
approved these changes
Aug 11, 2026
filak-sap
left a comment
Contributor
There was a problem hiding this comment.
Well done! Thank you very much for your contribution. Excellent summary helping us to save time re-learning the odata stuff.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Hi — thanks for maintaining pyodata.
Problem
EdmStringTypTraits.to_literal()wraps the value in single quotes but does not escape quotes inside the value:An embedded single quote in an OData string literal has to be doubled (
''), so this produces a query the service refuses. Any keyword-argument or callable filter over a value containing an apostrophe is affected — which in practice means a lot of French, Italian and Irish text (l'Etat,dell',O'Brien).Reproduction against a live service
Filtering the Swiss parliament's public OData v2 endpoint (
ws.parlament.ch, via swissparlpy) onTitle eq "délit d'initié":Position 45 is the apostrophe in
d'initié. With this patch the identical call returns HTTP 200 and the expected row.Change
to_literal: double any'in the value.from_literal: collapse''back to', so the two directions round-trip. This felt in scope since model: fix stripping single-quotes from string values #309 just tightenedfrom_literal, but I am happy to drop it and keep this PR to the wire-format bug alone if you would rather treat the inbound direction separately.Notes on decisions
to_literalnow callsstr(value)explicitly, because the%sinterpolation that used to stringify implicitly is replaced by a.replace()call. Accepted types are unchanged.pyodata/v4/, having only verified the v2 path.Edm.Stringliterals.Tests
Added to
test_traitsintests/test_model_v2.py, next to thefrom_literalcases from #309:to_literaldoubling, and a round-trip loop over["O'Brien", "l'Etat", "a''b", "'wrapped'", 'no quotes', ''].Full suite passes: 295 passed.
pylint/flake8fromdev-requirements.txtcould not run in my environment (Python 3.12 — the pinned versions fail on the removedpkg_resourcesand on theimportlib.metadataentry-points API); this is independent of the change, and no added line exceeds 120 columns.CHANGELOG updated under
[Unreleased] → Fixed.