Skip to content
GitLab
Explore
Sign in
Register
Primary navigation
Search or go to…
Project
C
caosdb-advanced-user-tools
Manage
Activity
Members
Labels
Plan
Issues
Issue boards
Milestones
Iterations
Wiki
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Snippets
Locked files
Build
Pipelines
Jobs
Pipeline schedules
Artifacts
Deploy
Releases
Package Registry
Container Registry
Model registry
Operate
Environments
Terraform modules
Monitor
Incidents
Analyze
Value stream analytics
Contributor analytics
CI/CD analytics
Repository analytics
Code review analytics
Issue analytics
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
caosdb
Software
caosdb-advanced-user-tools
Commits
edc0986f
Verified
Commit
edc0986f
authored
1 year ago
by
Daniel Hornung
Browse files
Options
Downloads
Patches
Plain Diff
ENH: jsex: multiple choice option for "do_not_create" list props.
parent
fb1f78d8
No related branches found
Branches containing commit
No related tags found
Tags containing commit
2 merge requests
!89
ENH: JsonSchemaExporter accepts do_not_create parameter.
,
!85
more jsonschema export
Pipeline
#43515
failed
1 year ago
Stage: setup
Stage: cert
Stage: style
Stage: unittest
Stage: integrationtest
Changes
2
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
src/caosadvancedtools/json_schema_exporter.py
+64
-24
64 additions, 24 deletions
src/caosadvancedtools/json_schema_exporter.py
unittests/test_json_schema_exporter.py
+69
-9
69 additions, 9 deletions
unittests/test_json_schema_exporter.py
with
133 additions
and
33 deletions
src/caosadvancedtools/json_schema_exporter.py
+
64
−
24
View file @
edc0986f
...
...
@@ -25,7 +25,7 @@ The scope of this json schema is the automatic generation of user interfaces.
"""
from
collections
import
OrderedDict
from
typing
import
Any
,
Dict
,
Iterable
,
List
,
Optional
,
Union
from
typing
import
Any
,
Dict
,
Iterable
,
List
,
Optional
,
Tuple
,
Union
import
linkahead
as
db
from
linkahead.common.datatype
import
get_list_datatype
,
is_list_datatype
...
...
@@ -43,6 +43,7 @@ class JsonSchemaExporter:
do_not_create
:
List
[
str
]
=
None
,
do_not_retrieve
:
List
[
str
]
=
None
,
no_remote
:
bool
=
False
,
multiple_choice
:
List
[
str
]
=
None
,
):
"""
Set up a JsonSchemaExporter, which can then be applied on RecordTypes.
...
...
@@ -75,6 +76,10 @@ class JsonSchemaExporter:
parameter, the behavior is undefined.
no_remote : bool
If True, do not attempt to connect to a LinkAhead server at all. Default is False.
multiple_choice : list[str], optional
A list of reference Property names which shall be denoted as multiple choice properties.
This means that each option in this property may be selected at most once. This is not
implemented yet if the Property is not in ``do_not_create`` as well.
"""
if
not
additional_options_for_text_props
:
additional_options_for_text_props
=
{}
...
...
@@ -82,6 +87,8 @@ class JsonSchemaExporter:
do_not_create
=
[]
if
not
do_not_retrieve
:
do_not_retrieve
=
[]
if
not
multiple_choice
:
multiple_choice
=
[]
self
.
_additional_properties
=
additional_properties
self
.
_name_property_for_new_records
=
name_property_for_new_records
...
...
@@ -91,6 +98,8 @@ class JsonSchemaExporter:
self
.
_do_not_create
=
do_not_create
self
.
_do_not_retrieve
=
do_not_retrieve
self
.
_no_remote
=
no_remote
self
.
_multiple_choice
=
multiple_choice
self
.
_rjsf_uischema
:
dict
=
{}
@staticmethod
def
_make_required_list
(
rt
:
db
.
RecordType
):
...
...
@@ -106,15 +115,15 @@ class JsonSchemaExporter:
return
required_list
def
_make_segment_from_prop
(
self
,
prop
:
db
.
Property
):
"""
Return the JSON Schema segment for the given property
def
_make_segment_from_prop
(
self
,
prop
:
db
.
Property
)
->
Tuple
[
OrderedDict
,
dict
]
:
"""
Return the JSON Schema
and ui schema
segment
s
for the given property
.
Parameters
----------
prop : db.Property
The property to be transformed.
"""
ui_schema
:
dict
=
{}
if
prop
.
datatype
==
db
.
TEXT
or
prop
.
datatype
==
db
.
DATETIME
:
text_format
=
None
text_pattern
=
None
...
...
@@ -131,7 +140,7 @@ class JsonSchemaExporter:
# options list.
text_format
=
[
"
date
"
,
"
date-time
"
]
return
self
.
_make_text_property
(
prop
.
description
,
text_format
,
text_pattern
)
return
self
.
_make_text_property
(
prop
.
description
,
text_format
,
text_pattern
)
,
ui_schema
json_prop
=
OrderedDict
()
if
prop
.
description
:
...
...
@@ -154,13 +163,21 @@ class JsonSchemaExporter:
json_prop
[
"
type
"
]
=
"
array
"
list_element_prop
=
db
.
Property
(
name
=
prop
.
name
,
datatype
=
get_list_datatype
(
prop
.
datatype
,
strict
=
True
))
json_prop
[
"
items
"
]
=
self
.
_make_segment_from_prop
(
list_element_prop
)
json_prop
[
"
items
"
],
inner_ui_schema
=
self
.
_make_segment_from_prop
(
list_element_prop
)
if
prop
.
name
in
self
.
_multiple_choice
and
prop
.
name
in
self
.
_do_not_create
:
json_prop
[
"
uniqueItems
"
]
=
True
ui_schema
[
"
ui:widget
"
]
=
"
checkboxes
"
ui_schema
[
"
ui:options
"
]
=
{
"
inline
"
:
True
}
if
inner_ui_schema
:
ui_schema
[
"
items
"
]
=
inner_ui_schema
elif
prop
.
is_reference
():
if
prop
.
datatype
==
db
.
REFERENCE
:
# No Record creation since no RT is specified and we don't know what
# schema to use, so only enum of all Records and all Files.
values
=
self
.
_retrieve_enum_values
(
"
RECORD
"
)
+
self
.
_retrieve_enum_values
(
"
FILE
"
)
json_prop
[
"
enum
"
]
=
values
if
prop
.
name
in
self
.
_multiple_choice
:
json_prop
[
"
uniqueItems
"
]
=
True
elif
prop
.
datatype
==
db
.
FILE
:
json_prop
[
"
type
"
]
=
"
string
"
json_prop
[
"
format
"
]
=
"
data-url
"
...
...
@@ -181,7 +198,9 @@ class JsonSchemaExporter:
else
:
rt
=
db
.
execute_query
(
f
"
FIND RECORDTYPE WITH name=
'
{
prop_name
}
'"
,
unique
=
True
)
subschema
=
self
.
_make_segment_from_recordtype
(
rt
)
subschema
,
ui_schema
=
self
.
_make_segment_from_recordtype
(
rt
)
# if inner_ui_schema:
# ui_schema = inner_ui_schema
if
values
:
subschema
[
"
title
"
]
=
"
Create new
"
json_prop
[
"
oneOf
"
]
=
[
...
...
@@ -198,7 +217,7 @@ class JsonSchemaExporter:
raise
ValueError
(
f
"
Unknown or no property datatype. Property
{
prop
.
name
}
with type
{
prop
.
datatype
}
"
)
return
json_prop
return
json_prop
,
ui_schema
@staticmethod
def
_make_text_property
(
description
=
""
,
text_format
=
None
,
text_pattern
=
None
):
...
...
@@ -245,16 +264,20 @@ class JsonSchemaExporter:
return
vals
def
_make_segment_from_recordtype
(
self
,
rt
:
db
.
RecordType
):
"""
Return
a
Json schema segment for the given RecordType.
def
_make_segment_from_recordtype
(
self
,
rt
:
db
.
RecordType
)
->
Tuple
[
OrderedDict
,
dict
]
:
"""
Return Json schema
and uischema
segment
s
for the given RecordType.
"""
schema
:
d
ict
[
str
,
Any
]
=
{
schema
:
OrderedD
ict
[
str
,
Any
]
=
OrderedDict
(
{
"
type
"
:
"
object
"
}
})
ui_schema
=
{}
schema
[
"
required
"
]
=
self
.
_make_required_list
(
rt
)
schema
[
"
additionalProperties
"
]
=
self
.
_additional_properties
if
rt
.
name
:
schema
[
"
title
"
]
=
rt
.
name
props
=
OrderedDict
()
if
self
.
_name_property_for_new_records
:
props
[
"
name
"
]
=
self
.
_make_text_property
(
"
The name of the Record to be created
"
)
...
...
@@ -269,13 +292,15 @@ class JsonSchemaExporter:
"
Creating a schema for multi-properties is not specified.
"
f
"
Property
{
prop
.
name
}
occurs more than once.
"
)
props
[
prop
.
name
]
=
self
.
_make_segment_from_prop
(
prop
)
props
[
prop
.
name
],
inner_ui_schema
=
self
.
_make_segment_from_prop
(
prop
)
if
inner_ui_schema
:
ui_schema
[
prop
.
name
]
=
inner_ui_schema
schema
[
"
properties
"
]
=
props
return
schema
return
schema
,
ui_schema
def
recordtype_to_json_schema
(
self
,
rt
:
db
.
RecordType
):
def
recordtype_to_json_schema
(
self
,
rt
:
db
.
RecordType
,
rjsf_uischema
:
Optional
[
dict
]
=
None
):
"""
Create a jsonschema from a given RecordType that can be used, e.g., to
validate a json specifying a record of the given type.
...
...
@@ -283,6 +308,9 @@ class JsonSchemaExporter:
----------
rt : RecordType
The RecordType from which a json schema will be created.
rjsf_uischema : dict, optional
If given, uiSchema definitions for react-jsonschema-forms will be appended to this dict
at ``[rt.name]``.
Returns
-------
...
...
@@ -292,10 +320,13 @@ class JsonSchemaExporter:
if
rt
is
None
:
raise
ValueError
(
"
recordtype_to_json_schema(...) cannot be called with a `None` RecordType.
"
)
schema
=
self
.
_make_segment_from_recordtype
(
rt
)
schema
[
"
$schema
"
]
=
"
https://json-schema.org/draft/2019-09/schema
"
if
rt
.
name
:
schema
[
"
title
"
]
=
rt
.
name
if
rjsf_uischema
is
None
:
rjsf_uischema
=
{}
schema
,
inner_uischema
=
self
.
_make_segment_from_recordtype
(
rt
)
if
inner_uischema
:
rjsf_uischema
[
rt
.
name
]
=
inner_uischema
schema
[
"
$schema
"
]
=
"
https://json-schema.org/draft/2020-12/schema
"
if
rt
.
description
:
schema
[
"
description
"
]
=
rt
.
description
...
...
@@ -310,6 +341,8 @@ def recordtype_to_json_schema(rt: db.RecordType, additional_properties: bool = T
do_not_create
:
List
[
str
]
=
None
,
do_not_retrieve
:
List
[
str
]
=
None
,
no_remote
:
bool
=
False
,
multiple_choice
:
List
[
str
]
=
None
,
rjsf_uischema
:
Optional
[
dict
]
=
None
,
):
"""
Create a jsonschema from a given RecordType that can be used, e.g., to
validate a json specifying a record of the given type.
...
...
@@ -338,16 +371,22 @@ def recordtype_to_json_schema(rt: db.RecordType, additional_properties: bool = T
description of the corresponding schema entry. If set to false, an
additional `unit` key is added to the schema itself which is purely
annotational and ignored, e.g., in validation. Default is True.
do_not_create : list[str]
do_not_create : list[str]
, optional
A list of RedcordType names, for which there should be no option
to create them. Instead, only the choice of existing elements should
be given.
do_not_retrieve : list[str]
do_not_retrieve : list[str]
, optional
A list of RedcordType names, for which no Records shall be retrieved. Instead, only an
object description should be given. If this list overlaps with the `do_not_create`
parameter, the behavior is undefined.
no_remote : bool
no_remote : bool
, optional
If True, do not attempt to connect to a LinkAhead server at all. Default is False.
multiple_choice : list[str], optional
A list of reference Property names which shall be denoted as multiple choice properties.
This means that each option in this property may be selected at most once. This is not
implemented yet if the Property is not in ``do_not_create`` as well.
rjsf_uischema : dict, optional
If given, uiSchema definitions for react-jsonschema-forms will be appended to this dict.
Returns
-------
...
...
@@ -365,8 +404,9 @@ def recordtype_to_json_schema(rt: db.RecordType, additional_properties: bool = T
do_not_create
=
do_not_create
,
do_not_retrieve
=
do_not_retrieve
,
no_remote
=
no_remote
,
multiple_choice
=
multiple_choice
,
)
return
exporter
.
recordtype_to_json_schema
(
rt
)
return
exporter
.
recordtype_to_json_schema
(
rt
,
rjsf_uischema
=
rjsf_uischema
)
def
make_array
(
schema
:
dict
)
->
dict
:
...
...
@@ -397,7 +437,7 @@ out : dict
result
=
{
"
type
"
:
"
array
"
,
"
items
"
:
schema
,
"
$schema
"
:
"
https://json-schema.org/draft/20
19-09
/schema
"
,
"
$schema
"
:
"
https://json-schema.org/draft/20
20-12
/schema
"
,
}
return
result
...
...
This diff is collapsed.
Click to expand it.
unittests/test_json_schema_exporter.py
+
69
−
9
View file @
edc0986f
...
...
@@ -81,6 +81,19 @@ def _mock_execute_query(query_string, unique=False, **kwargs):
return
referencing_type_records
# wrong types, but who cares for the test?
elif
query_string
==
"
FIND RECORDTYPE WITH name=
'
RT1
'"
and
unique
is
True
:
return
RT1
elif
query_string
==
"
FIND RECORDTYPE WITH name=
'
RT21
'"
and
unique
is
True
:
model_str
=
"""
RT1:
RT21:
obligatory_properties:
RT1:
datatype: LIST<RT1>
RT3:
obligatory_properties:
RT21:
"""
model
=
parse_model_from_string
(
model_str
)
return
model
.
get_deep
(
"
RT21
"
)
elif
query_string
==
"
SELECT name, id FROM RECORD
"
:
return
all_records
elif
query_string
==
"
SELECT name, id FROM FILE
"
:
...
...
@@ -602,6 +615,7 @@ RT2:
"
some_date
"
],
"
additionalProperties
"
: true,
"
title
"
:
"
RT1
"
,
"
properties
"
: {
"
some_date
"
: {
"
description
"
:
"
Just some date
"
,
...
...
@@ -617,8 +631,7 @@ RT2:
]
}
},
"
$schema
"
:
"
https://json-schema.org/draft/2019-09/schema
"
,
"
title
"
:
"
RT1
"
,
"
$schema
"
:
"
https://json-schema.org/draft/2020-12/schema
"
,
"
description
"
:
"
some description
"
}
"""
# Second test: with reference
...
...
@@ -630,6 +643,7 @@ RT2:
"
RT1
"
],
"
additionalProperties
"
: true,
"
title
"
:
"
RT2
"
,
"
properties
"
: {
"
RT1
"
: {
"
description
"
:
"
some description
"
,
...
...
@@ -647,6 +661,7 @@ RT2:
"
some_date
"
],
"
additionalProperties
"
: true,
"
title
"
:
"
Create new
"
,
"
properties
"
: {
"
some_date
"
: {
"
description
"
:
"
Just some date
"
,
...
...
@@ -661,14 +676,12 @@ RT2:
}
]
}
},
"
title
"
:
"
Create new
"
}
}
]
}
},
"
$schema
"
:
"
https://json-schema.org/draft/2019-09/schema
"
,
"
title
"
:
"
RT2
"
"
$schema
"
:
"
https://json-schema.org/draft/2020-12/schema
"
}
"""
# Third test: Reference prop shall be only existing references, no option to create new ones.
...
...
@@ -679,6 +692,7 @@ RT2:
"
RT1
"
],
"
additionalProperties
"
: true,
"
title
"
:
"
RT2
"
,
"
properties
"
: {
"
RT1
"
: {
"
description
"
:
"
some description
"
,
...
...
@@ -688,8 +702,7 @@ RT2:
]
}
},
"
$schema
"
:
"
https://json-schema.org/draft/2019-09/schema
"
,
"
title
"
:
"
RT2
"
"
$schema
"
:
"
https://json-schema.org/draft/2020-12/schema
"
}
"""
...
...
@@ -820,6 +833,53 @@ RT3:
assert
(
schema_noexist_noremote
[
"
properties
"
][
"
NoRecords
"
].
get
(
"
properties
"
)
==
OrderedDict
([(
'
some_text
'
,
{
'
type
'
:
'
string
'
})]))
schema_noexist_noretrieve
=
rtjs
(
model
.
get_deep
(
"
RT2
"
),
do_not_retrieve
=
[
"
RT1
"
])
uischema
=
{}
schema_noexist_noretrieve
=
rtjs
(
model
.
get_deep
(
"
RT2
"
),
do_not_retrieve
=
[
"
RT1
"
],
rjsf_uischema
=
uischema
)
assert
schema_noexist_noretrieve
[
"
properties
"
][
"
RT1
"
].
get
(
"
type
"
)
==
"
object
"
assert
"
some_date
"
in
schema_noexist_noretrieve
[
"
properties
"
][
"
RT1
"
].
get
(
"
properties
"
)
assert
not
uischema
@patch
(
"
linkahead.execute_query
"
,
new
=
Mock
(
side_effect
=
_mock_execute_query
))
def
test_multiple_choice
():
"""
Multiple choice is mostyly a matter of UI.
"""
model_str
=
"""
RT1:
RT21:
obligatory_properties:
RT1:
datatype: LIST<RT1>
RT3:
obligatory_properties:
RT21:
RT4:
obligatory_properties:
RT21:
datatype: LIST<RT21>
"""
model
=
parse_model_from_string
(
model_str
)
# generate a multiple choice, in first level
uischema
=
{}
schema
=
rtjs
(
model
.
get_deep
(
"
RT21
"
),
additional_properties
=
False
,
do_not_create
=
[
"
RT1
"
],
multiple_choice
=
[
"
RT1
"
],
rjsf_uischema
=
uischema
)
assert
schema
[
"
properties
"
][
"
RT1
"
][
"
uniqueItems
"
]
is
True
assert
(
str
(
uischema
[
"
RT21
"
])
==
"
{
'
RT1
'
: {
'
ui:widget
'
:
'
checkboxes
'
,
'
ui:options
'
: {
'
inline
'
: True}}}
"
)
# second level
uischema
=
{}
schema
=
rtjs
(
model
.
get_deep
(
"
RT3
"
),
additional_properties
=
False
,
do_not_create
=
[
"
RT1
"
],
multiple_choice
=
[
"
RT1
"
],
rjsf_uischema
=
uischema
)
assert
schema
[
"
properties
"
][
"
RT21
"
][
"
properties
"
][
"
RT1
"
][
"
uniqueItems
"
]
is
True
assert
(
str
(
uischema
[
"
RT3
"
])
==
"
{
'
RT21
'
: {
'
RT1
'
: {
'
ui:widget
'
:
'
checkboxes
'
,
'
ui:options
'
: {
'
inline
'
: True}}}}
"
)
# second level with lists
uischema
=
{}
schema
=
rtjs
(
model
.
get_deep
(
"
RT4
"
),
additional_properties
=
False
,
do_not_create
=
[
"
RT1
"
],
multiple_choice
=
[
"
RT1
"
],
rjsf_uischema
=
uischema
)
assert
schema
[
"
properties
"
][
"
RT21
"
][
"
items
"
][
"
properties
"
][
"
RT1
"
][
"
uniqueItems
"
]
is
True
assert
(
str
(
uischema
[
"
RT4
"
])
==
"
{
'
RT21
'
: {
'
items
'
: {
'
RT1
'
: {
'
ui:widget
'
:
'
checkboxes
'
,
"
"'
ui:options
'
: {
'
inline
'
: True}}}}}
"
)
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment