Skip to content

model: escape single quotes in Edm.String literals - #313

Merged
filak-sap merged 1 commit into
SAP:masterfrom
lefranz:fix/escape-single-quotes-in-string-literals
Aug 11, 2026
Merged

model: escape single quotes in Edm.String literals#313
filak-sap merged 1 commit into
SAP:masterfrom
lefranz:fix/escape-single-quotes-in-string-literals

Conversation

@lefranz

@lefranz lefranz commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Hi — thanks for maintaining pyodata.

Problem

EdmStringTypTraits.to_literal() wraps the value in single quotes but does not escape quotes inside the value:

>>> from pyodata.v2.model import EdmStringTypTraits
>>> EdmStringTypTraits().to_literal("délit d'initié")
"'délit d'initié'"          # the literal ends at the apostrophe

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) on Title eq "délit d'initié":

GET /odata.svc/Business?$filter=Language eq 'FR' and Title eq 'délit d'initié'
→ HTTP 400
  {"error":{"message":{"value":"Syntax error at position 45 in
   'Language eq 'FR' and Title 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 tightened from_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_literal now calls str(value) explicitly, because the %s interpolation that used to stringify implicitly is replaced by a .replace() call. Accepted types are unchanged.
  • I did not touch pyodata/v4/, having only verified the v2 path.
  • Escaping is applied in the traits rather than at the filter-construction sites, so it covers every caller that goes through Edm.String literals.

Tests

Added to test_traits in tests/test_model_v2.py, next to the from_literal cases from #309: to_literal doubling, and a round-trip loop over ["O'Brien", "l'Etat", "a''b", "'wrapped'", 'no quotes', ''].

Full suite passes: 295 passed. pylint/flake8 from dev-requirements.txt could not run in my environment (Python 3.12 — the pinned versions fail on the removed pkg_resources and on the importlib.metadata entry-points API); this is independent of the change, and no added line exceeds 120 columns.

CHANGELOG updated under [Unreleased] → Fixed.

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.
@cla-assistant

cla-assistant Bot commented Aug 10, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@filak-sap filak-sap left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well done! Thank you very much for your contribution. Excellent summary helping us to save time re-learning the odata stuff.

@filak-sap
filak-sap merged commit c8258ee into SAP:master Aug 11, 2026
42 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants