Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/).

## [Unreleased]

### Fixed

- model: escape single quotes in `Edm.String` literals, so filters on values containing an apostrophe produce a valid OData query - Francois Pilet


## [1.12.0]

Expand Down
4 changes: 2 additions & 2 deletions pyodata/v2/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -570,15 +570,15 @@ class EdmStringTypTraits(TypTraits):

# pylint: disable=no-self-use
def to_literal(self, value):
return '\'%s\'' % (value)
return '\'%s\'' % (str(value).replace("'", "''"))

# pylint: disable=no-self-use
def from_json(self, value):
return value

def from_literal(self, value):
if len(value) >= 2 and value[0] == "'" and value[-1] == "'":
return value[1:-1]
return value[1:-1].replace("''", "'")
return value


Expand Down
11 changes: 11 additions & 0 deletions tests/test_model_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,17 @@ def test_traits():
assert typ.traits.from_literal("only right'") == "only right'"
assert typ.traits.from_literal("'") == "'"

# EdmStringTypTraits.to_literal — a single quote inside the value must be doubled,
# otherwise the resulting literal terminates early and the server rejects the query
assert typ.traits.to_literal("O'Brien") == "'O''Brien'"
assert typ.traits.to_literal("l'Etat") == "'l''Etat'"
assert typ.traits.to_literal("''") == "''''''"
assert typ.traits.to_literal('no quotes') == "'no quotes'"

# EdmStringTypTraits — to_literal/from_literal must round-trip
for value in ["O'Brien", "l'Etat", "a''b", "'wrapped'", 'no quotes', '']:
assert typ.traits.from_literal(typ.traits.to_literal(value)) == value

# bool
typ = Types.from_name('Edm.Boolean')
assert repr(typ.traits) == 'EdmBooleanTypTraits'
Expand Down
Loading