Skip to content

Commit ef25d00

Browse files
authored
Merge pull request #283 from cwacek/fix/229
feature: Add support for the const keyword.
2 parents be352cd + dd1a825 commit ef25d00

File tree

5 files changed

+96
-5
lines changed

5 files changed

+96
-5
lines changed

python_jsonschema_objects/classbuilder.py

+27-3
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import logging
55
import sys
66

7+
import jsonschema.exceptions
78
import referencing._core
89
import six
910

@@ -184,11 +185,23 @@ def __init__(self, **props):
184185
# but only for the ones that have defaults set.
185186
for name in self.__has_default__:
186187
if name not in props:
187-
default_value = copy.deepcopy(self.__propinfo__[name]["default"])
188+
# "defaults" could come from either the 'default' keyword or the 'const' keyword
189+
try:
190+
default_value = self.__propinfo__[name]["default"]
191+
except KeyError:
192+
try:
193+
default_value = self.__propinfo__[name]["const"]
194+
except KeyError:
195+
raise jsonschema.exceptions.SchemaError(
196+
"Schema parsing error. Expected {0} to have default or const value".format(
197+
name
198+
)
199+
)
200+
188201
logger.debug(
189202
util.lazy_format("Initializing '{0}' to '{1}'", name, default_value)
190203
)
191-
setattr(self, name, default_value)
204+
setattr(self, name, copy.deepcopy(default_value))
192205

193206
for prop in props:
194207
try:
@@ -626,7 +639,7 @@ def _build_literal(self, nm, clsdata):
626639
"__propinfo__": {
627640
"__literal__": clsdata,
628641
"__title__": clsdata.get("title"),
629-
"__default__": clsdata.get("default"),
642+
"__default__": clsdata.get("default") or clsdata.get("const"),
630643
}
631644
},
632645
)
@@ -670,6 +683,17 @@ def _build_object(self, nm, clsdata, parents, **kw):
670683
)
671684
defaults.add(prop)
672685

686+
if "const" in detail:
687+
logger.debug(
688+
util.lazy_format(
689+
"Setting const for {0}.{1} to: {2}",
690+
nm,
691+
prop,
692+
detail["const"],
693+
)
694+
)
695+
defaults.add(prop)
696+
673697
if detail.get("type", None) == "object":
674698
uri = "{0}/{1}_{2}".format(nm, prop, "<anonymous>")
675699
self.resolved[uri] = self.construct(uri, detail, (ProtocolBase,), **kw)

python_jsonschema_objects/literals.py

+8
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ def __init__(self, value, typ=None):
4444

4545
self.validate()
4646

47+
constval = self.const()
48+
if constval is not None:
49+
self._value = constval
50+
4751
def as_dict(self):
4852
return self.for_json()
4953

@@ -54,6 +58,10 @@ def for_json(self):
5458
def default(cls):
5559
return cls.__propinfo__.get("__default__")
5660

61+
@classmethod
62+
def const(cls):
63+
return cls.__propinfo__.get("__literal__", {}).get("const", None)
64+
5765
@classmethod
5866
def propinfo(cls, propname):
5967
if propname not in cls.__propinfo__:

python_jsonschema_objects/validators.py

+6
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,12 @@ def enum(param, value, _):
5858
raise ValidationError("{0} is not one of {1}".format(value, param))
5959

6060

61+
@registry.register()
62+
def const(param, value, _):
63+
if value != param:
64+
raise ValidationError("{0} is not constant {1}".format(value, param))
65+
66+
6167
@registry.register()
6268
def minimum(param, value, type_data):
6369
exclusive = type_data.get("exclusiveMinimum")

test/test_229.py

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import pytest
2+
3+
import python_jsonschema_objects as pjo
4+
5+
6+
def test_const_properties():
7+
schema = {
8+
"title": "Example",
9+
"type": "object",
10+
"properties": {
11+
"url": {
12+
"type": "string",
13+
"default": "https://example.com/your-username/my-project",
14+
},
15+
"type": {"type": "string", "const": "git"},
16+
},
17+
}
18+
19+
ob = pjo.ObjectBuilder(schema)
20+
ns1 = ob.build_classes()
21+
ex = ns1.Example()
22+
ex.url = "can be anything"
23+
24+
# we expect the value to be set already for const values
25+
assert ex.type == "git"
26+
with pytest.raises(pjo.ValidationError):
27+
# Trying to set the value to something else should throw validation errors
28+
ex.type = "mercurial"
29+
30+
# setting the value to the const value is a no-op, but permitted
31+
ex.type = "git"
32+
33+
34+
def test_const_bare_type():
35+
schema = {
36+
"title": "Example",
37+
"type": "string",
38+
"const": "I stand alone",
39+
}
40+
41+
ob = pjo.ObjectBuilder(schema)
42+
ns1 = ob.build_classes()
43+
ex = ns1.Example("I stand alone")
44+
# we expect the value to be set already for const values
45+
assert ex == "I stand alone"
46+
with pytest.raises(pjo.ValidationError):
47+
# Trying to set the value to something else should throw validation errors
48+
ex = ns1.Example("mercurial")
49+
50+
# setting the value to the const value is a no-op, but permitted
51+
ex = ns1.Example("I stand alone")

test/test_default_values.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -53,15 +53,17 @@ def test_nullable_types_are_still_nullable(ns):
5353

5454
thing1.p1 = None
5555
thing1.validate()
56-
assert thing1.as_dict() == {"p1": 0, "p2": None}
56+
assert thing1.as_dict() == {"p1": None, "p2": None}
5757

5858

5959
def test_null_types_without_defaults_do_not_serialize(ns):
6060
thing1 = ns.DefaultTest()
6161

62+
assert thing1.as_dict() == {"p1": 0, "p2": None}
63+
6264
thing1.p3 = 10
6365
thing1.validate()
6466
thing1.p1 = None
6567
thing1.validate()
6668

67-
assert thing1.as_dict() == {"p1": 0, "p2": None, "p3": 10}
69+
assert thing1.as_dict() == {"p1": None, "p2": None, "p3": 10}

0 commit comments

Comments
 (0)