shacl2code 0.0.9__tar.gz → 0.0.11__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- shacl2code-0.0.11/PKG-INFO +205 -0
- shacl2code-0.0.11/README.md +174 -0
- {shacl2code-0.0.9 → shacl2code-0.0.11}/pyproject.toml +1 -1
- {shacl2code-0.0.9 → shacl2code-0.0.11}/src/shacl2code/lang/common.py +58 -34
- {shacl2code-0.0.9 → shacl2code-0.0.11}/src/shacl2code/lang/jsonschema.py +2 -2
- {shacl2code-0.0.9 → shacl2code-0.0.11}/src/shacl2code/lang/python.py +2 -1
- {shacl2code-0.0.9 → shacl2code-0.0.11}/src/shacl2code/lang/templates/jsonschema.j2 +38 -42
- {shacl2code-0.0.9 → shacl2code-0.0.11}/src/shacl2code/lang/templates/python.j2 +515 -200
- {shacl2code-0.0.9 → shacl2code-0.0.11}/src/shacl2code/model.py +41 -47
- shacl2code-0.0.11/src/shacl2code/version.py +1 -0
- {shacl2code-0.0.9 → shacl2code-0.0.11}/tests/conftest.py +23 -1
- {shacl2code-0.0.9 → shacl2code-0.0.11}/tests/data/model/test-context.json +12 -0
- {shacl2code-0.0.9 → shacl2code-0.0.11}/tests/data/model/test.ttl +76 -3
- shacl2code-0.0.11/tests/data/python/bad-object-type-inline.json +13 -0
- shacl2code-0.0.11/tests/data/python/bad-object-type-ref-after.json +14 -0
- shacl2code-0.0.11/tests/data/python/bad-object-type-ref-before.json +14 -0
- {shacl2code-0.0.9 → shacl2code-0.0.11}/tests/data/python/roundtrip.json +4 -0
- {shacl2code-0.0.9 → shacl2code-0.0.11}/tests/expect/jsonschema/test-context.json +392 -60
- {shacl2code-0.0.9 → shacl2code-0.0.11}/tests/expect/jsonschema/test.json +392 -60
- {shacl2code-0.0.9 → shacl2code-0.0.11}/tests/expect/python/test-context.py +625 -245
- {shacl2code-0.0.9 → shacl2code-0.0.11}/tests/expect/python/test.py +622 -236
- {shacl2code-0.0.9 → shacl2code-0.0.11}/tests/expect/raw/test-context.txt +11 -1
- shacl2code-0.0.11/tests/expect/raw/test.txt +24 -0
- {shacl2code-0.0.9 → shacl2code-0.0.11}/tests/test_jsonschema.py +32 -0
- {shacl2code-0.0.9 → shacl2code-0.0.11}/tests/test_python.py +401 -41
- shacl2code-0.0.9/PKG-INFO +0 -96
- shacl2code-0.0.9/README.md +0 -66
- shacl2code-0.0.9/src/shacl2code/version.py +0 -1
- shacl2code-0.0.9/tests/expect/raw/test.txt +0 -19
- {shacl2code-0.0.9 → shacl2code-0.0.11}/.flake8 +0 -0
- {shacl2code-0.0.9 → shacl2code-0.0.11}/.github/workflows/coverage-generate.yaml +0 -0
- {shacl2code-0.0.9 → shacl2code-0.0.11}/.github/workflows/coverage-report.yaml +0 -0
- {shacl2code-0.0.9 → shacl2code-0.0.11}/.github/workflows/publish.yaml +0 -0
- {shacl2code-0.0.9 → shacl2code-0.0.11}/.github/workflows/test.yaml +0 -0
- {shacl2code-0.0.9 → shacl2code-0.0.11}/.gitignore +0 -0
- {shacl2code-0.0.9 → shacl2code-0.0.11}/LICENSE +0 -0
- {shacl2code-0.0.9 → shacl2code-0.0.11}/src/shacl2code/__init__.py +0 -0
- {shacl2code-0.0.9 → shacl2code-0.0.11}/src/shacl2code/__main__.py +0 -0
- {shacl2code-0.0.9 → shacl2code-0.0.11}/src/shacl2code/context.py +0 -0
- {shacl2code-0.0.9 → shacl2code-0.0.11}/src/shacl2code/lang/__init__.py +0 -0
- {shacl2code-0.0.9 → shacl2code-0.0.11}/src/shacl2code/lang/jinja.py +0 -0
- {shacl2code-0.0.9 → shacl2code-0.0.11}/src/shacl2code/lang/lang.py +0 -0
- {shacl2code-0.0.9 → shacl2code-0.0.11}/src/shacl2code/main.py +0 -0
- {shacl2code-0.0.9 → shacl2code-0.0.11}/src/shacl2code/urlcontext.py +0 -0
- {shacl2code-0.0.9 → shacl2code-0.0.11}/tests/data/abort.j2 +0 -0
- {shacl2code-0.0.9 → shacl2code-0.0.11}/tests/data/bad-id.j2 +0 -0
- {shacl2code-0.0.9 → shacl2code-0.0.11}/tests/data/bad-node-kind.ttl +0 -0
- {shacl2code-0.0.9 → shacl2code-0.0.11}/tests/data/bad-pattern-class.ttl +0 -0
- {shacl2code-0.0.9 → shacl2code-0.0.11}/tests/data/bad-pattern-integer.ttl +0 -0
- {shacl2code-0.0.9 → shacl2code-0.0.11}/tests/data/bad-reference.jsonld +0 -0
- {shacl2code-0.0.9 → shacl2code-0.0.11}/tests/data/context-url.j2 +0 -0
- {shacl2code-0.0.9 → shacl2code-0.0.11}/tests/data/context.j2 +0 -0
- {shacl2code-0.0.9 → shacl2code-0.0.11}/tests/data/missing-range.ttl +0 -0
- {shacl2code-0.0.9 → shacl2code-0.0.11}/tests/data/python/links.json +0 -0
- {shacl2code-0.0.9 → shacl2code-0.0.11}/tests/data/raw.j2 +0 -0
- {shacl2code-0.0.9 → shacl2code-0.0.11}/tests/expect/make_expect.py +0 -0
- {shacl2code-0.0.9 → shacl2code-0.0.11}/tests/test_cli.py +0 -0
- {shacl2code-0.0.9 → shacl2code-0.0.11}/tests/test_common_jinja.py +0 -0
- {shacl2code-0.0.9 → shacl2code-0.0.11}/tests/test_common_prefix.py +0 -0
- {shacl2code-0.0.9 → shacl2code-0.0.11}/tests/test_context.py +0 -0
- {shacl2code-0.0.9 → shacl2code-0.0.11}/tests/test_model_source.py +0 -0
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: shacl2code
|
|
3
|
+
Version: 0.0.11
|
|
4
|
+
Summary: Convert SHACL model file to code bindings
|
|
5
|
+
Project-URL: Homepage, https://github.com/JPEWdev/shacl2code
|
|
6
|
+
Project-URL: Repository, https://github.com/JPEWdev/shacl2code.git
|
|
7
|
+
Project-URL: Issues, https://github.com/JPEWdev/shacl2code/issues
|
|
8
|
+
Author-email: Joshua Watt <JPEWhacker@gmail.com>
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Topic :: Software Development :: Build Tools
|
|
20
|
+
Requires-Python: >=3.8
|
|
21
|
+
Requires-Dist: jinja2>=3.1.2
|
|
22
|
+
Requires-Dist: rdflib>=7.0.0
|
|
23
|
+
Provides-Extra: dev
|
|
24
|
+
Requires-Dist: flake8>=7.0.0; extra == 'dev'
|
|
25
|
+
Requires-Dist: jsonschema>=4.21.1; extra == 'dev'
|
|
26
|
+
Requires-Dist: pyshacl>=0.25.0; extra == 'dev'
|
|
27
|
+
Requires-Dist: pytest-cov>=4.1; extra == 'dev'
|
|
28
|
+
Requires-Dist: pytest-server-fixtures>=1.7; extra == 'dev'
|
|
29
|
+
Requires-Dist: pytest>=7.4; extra == 'dev'
|
|
30
|
+
Description-Content-Type: text/markdown
|
|
31
|
+
|
|
32
|
+
# Convert SHACL Model to code bindings
|
|
33
|
+
[](https://htmlpreview.github.io/?https://github.com/JPEWdev/shacl2code/blob/python-coverage-comment-action-data/htmlcov/index.html)
|
|
34
|
+
|
|
35
|
+
This tool can be used to convert a SHACL model into various code bindings
|
|
36
|
+
|
|
37
|
+
## Installation
|
|
38
|
+
|
|
39
|
+
`shacl2code` can be installed using pip:
|
|
40
|
+
|
|
41
|
+
```shell
|
|
42
|
+
python3 -m pip install shacl2code
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Usage
|
|
46
|
+
|
|
47
|
+
`shacl2code` can generate bindings from either a local file:
|
|
48
|
+
```shell
|
|
49
|
+
shacl2code generate -i model.jsonld python -o out.py
|
|
50
|
+
```
|
|
51
|
+
Or from a URL:
|
|
52
|
+
```shell
|
|
53
|
+
shacl2code generate -i https://example.com/rdf/model.jsonld python -o out.py
|
|
54
|
+
```
|
|
55
|
+
Or from stdin:
|
|
56
|
+
```shell
|
|
57
|
+
cat model.jsonld | shacl2code generate -i - python -o - > out.py
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
For more information, run:
|
|
61
|
+
```shell
|
|
62
|
+
shacl2code --help
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
The available language bindings can be viewed by running:
|
|
66
|
+
```shell
|
|
67
|
+
shacl2code list
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Developing
|
|
71
|
+
|
|
72
|
+
Developing on `shacl2code` is best done using a virtual environment. You can
|
|
73
|
+
configure one and install shacl2code in editable mode with all necessary
|
|
74
|
+
development dependencies by running:
|
|
75
|
+
|
|
76
|
+
```shell
|
|
77
|
+
python3 -m venv .venv
|
|
78
|
+
. .venv/bin/activate
|
|
79
|
+
pip install -e ".[dev]"
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Testing
|
|
83
|
+
|
|
84
|
+
`shacl2code` has a test suite written in [pytest][pytest]. To run it, setup a
|
|
85
|
+
virtual environment as shown above, then run:
|
|
86
|
+
```shell
|
|
87
|
+
pytest
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
In addition to the test results, a test coverage report will also be generated
|
|
91
|
+
using [pytest-cov][pytest-cov]
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
## Custom Annotations
|
|
95
|
+
|
|
96
|
+
`shacl2code` supports a number of custom annotations that can be specified in a
|
|
97
|
+
SHACL model to give hints about the generated code. All of these annotations
|
|
98
|
+
live in the `https://jpewdev.github.io/shacl2code/schema#` namespace, and
|
|
99
|
+
commonly are given the `sh-to-code` prefix to make it easier to reference them.
|
|
100
|
+
For example, in Turtle one would add the prefix mapping:
|
|
101
|
+
|
|
102
|
+
```ttl
|
|
103
|
+
@prefix sh-to-code: <https://jpewdev.github.io/shacl2code/schema#> .
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### ID Property Name
|
|
107
|
+
|
|
108
|
+
The `idPropertyName` annotation allows a class to specify what the name of the
|
|
109
|
+
"property" that specifies the RDF subject of an object is for serializations
|
|
110
|
+
that support it. For example, in JSON-LD, the `@id` property indicates the
|
|
111
|
+
subject in RDF. If you wanted to alias the `@id` property to another name, the
|
|
112
|
+
`idPropertyName` annotation will let you do this. For example, the following
|
|
113
|
+
turtle will use `MyId` instead of `@id` when writing JSON-LD bindings:
|
|
114
|
+
|
|
115
|
+
```ttl
|
|
116
|
+
<MyClass> a owl:Class, sh:NodeShape ;
|
|
117
|
+
sh-to-code:idPropertyName "MyId"
|
|
118
|
+
.
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
When doing this, the class would then look like this in JSON-LD:
|
|
122
|
+
```json
|
|
123
|
+
{
|
|
124
|
+
"@type": "MyClass",
|
|
125
|
+
"MyId": "http://example.com/id"
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
The `idProperyName` annotation is inherited by derived classes, so for example
|
|
130
|
+
any class that derived from `MyClass` would also use `MyId` as the subject
|
|
131
|
+
property.
|
|
132
|
+
|
|
133
|
+
**Note:** This only specifies what the name of the field should be in generated
|
|
134
|
+
bindings and has no bearing on how an RDF parser would interpret the property.
|
|
135
|
+
In order to still be parsed by RDF, you would also need context file that maps
|
|
136
|
+
`MyId` to `@id`, for example:
|
|
137
|
+
|
|
138
|
+
```json
|
|
139
|
+
{
|
|
140
|
+
"@context": {
|
|
141
|
+
"MyId": "@id"
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
`shacl2code` doesn't do this for you, nor does it validate that you have done
|
|
147
|
+
it.
|
|
148
|
+
|
|
149
|
+
### Extensible Classes
|
|
150
|
+
|
|
151
|
+
Most bindings generated from `shacl2code` are "closed" in that they do not
|
|
152
|
+
allow extra properties to be added to object outside of what is specified in
|
|
153
|
+
model. This ensures that field name typos and other unintended properties are
|
|
154
|
+
not added to an object. However, in some cases a class may be specifically
|
|
155
|
+
intended to be extended such that arbitrary fields can be added to it, which
|
|
156
|
+
can be done using the `isExtensible` property. This is a boolean property that
|
|
157
|
+
indicates if a class can be extended, and defaults to `false`. For example, the
|
|
158
|
+
following turtle will declare a class as extensible:
|
|
159
|
+
|
|
160
|
+
```ttl
|
|
161
|
+
|
|
162
|
+
<MyClass> a owl:Class, sh:NodeShape ;
|
|
163
|
+
sh-to-code:isExtensible true
|
|
164
|
+
.
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
The `isExtensible` property is _not_ inherited by derived classes, meaning it
|
|
168
|
+
is possible to have a class derived from `MyClass` which is itself not
|
|
169
|
+
extensible.
|
|
170
|
+
|
|
171
|
+
The mechanism for dealing with extensible classes will vary between the
|
|
172
|
+
different bindings, but in general it means that they will not be very picky
|
|
173
|
+
about object types and properties in any location where an extensible class is
|
|
174
|
+
allowed.
|
|
175
|
+
|
|
176
|
+
**Note**: You may want to be careful about where and how many extensible
|
|
177
|
+
classes are allowed in your model. If there are too many and they are allowed
|
|
178
|
+
anywhere, it may mean that typos in object types (e.g. `@type` in JSON-LD) are
|
|
179
|
+
not caught by validation as they will have to be assumed to be a derived class
|
|
180
|
+
from an extensible type.
|
|
181
|
+
|
|
182
|
+
### Abstract Classes
|
|
183
|
+
|
|
184
|
+
By default, classes generated by `shacl2code` are all instantiable (i.e. they
|
|
185
|
+
can be created). In some instances, it may be desirable to declare a class as
|
|
186
|
+
abstract (meaning that it cannot be instantiated, but non-abstract derived
|
|
187
|
+
classes can). This can be done with the boolean `isAbstract` property. For
|
|
188
|
+
example, the following turtle will declare a class as abstract:
|
|
189
|
+
|
|
190
|
+
```ttl
|
|
191
|
+
|
|
192
|
+
<MyClass> a owl:Class, sh:NodeShape ;
|
|
193
|
+
sh-to-code:isAbstract true
|
|
194
|
+
.
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
The `isAbstract` property is _not_ inherited by derived classes, so any derived
|
|
198
|
+
classes are automatically concrete unless they indicate otherwise.
|
|
199
|
+
|
|
200
|
+
Note that for compatibility reasons, it is also possible to define a class as
|
|
201
|
+
abstract by declaring it to be of type: `http://spdx.invalid./AbstractClass`,
|
|
202
|
+
but this is not preferred.
|
|
203
|
+
|
|
204
|
+
[pytest]: https://www.pytest.org
|
|
205
|
+
[pytest-cov]: https://pytest-cov.readthedocs.io/en/latest/
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# Convert SHACL Model to code bindings
|
|
2
|
+
[](https://htmlpreview.github.io/?https://github.com/JPEWdev/shacl2code/blob/python-coverage-comment-action-data/htmlcov/index.html)
|
|
3
|
+
|
|
4
|
+
This tool can be used to convert a SHACL model into various code bindings
|
|
5
|
+
|
|
6
|
+
## Installation
|
|
7
|
+
|
|
8
|
+
`shacl2code` can be installed using pip:
|
|
9
|
+
|
|
10
|
+
```shell
|
|
11
|
+
python3 -m pip install shacl2code
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Usage
|
|
15
|
+
|
|
16
|
+
`shacl2code` can generate bindings from either a local file:
|
|
17
|
+
```shell
|
|
18
|
+
shacl2code generate -i model.jsonld python -o out.py
|
|
19
|
+
```
|
|
20
|
+
Or from a URL:
|
|
21
|
+
```shell
|
|
22
|
+
shacl2code generate -i https://example.com/rdf/model.jsonld python -o out.py
|
|
23
|
+
```
|
|
24
|
+
Or from stdin:
|
|
25
|
+
```shell
|
|
26
|
+
cat model.jsonld | shacl2code generate -i - python -o - > out.py
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
For more information, run:
|
|
30
|
+
```shell
|
|
31
|
+
shacl2code --help
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
The available language bindings can be viewed by running:
|
|
35
|
+
```shell
|
|
36
|
+
shacl2code list
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Developing
|
|
40
|
+
|
|
41
|
+
Developing on `shacl2code` is best done using a virtual environment. You can
|
|
42
|
+
configure one and install shacl2code in editable mode with all necessary
|
|
43
|
+
development dependencies by running:
|
|
44
|
+
|
|
45
|
+
```shell
|
|
46
|
+
python3 -m venv .venv
|
|
47
|
+
. .venv/bin/activate
|
|
48
|
+
pip install -e ".[dev]"
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Testing
|
|
52
|
+
|
|
53
|
+
`shacl2code` has a test suite written in [pytest][pytest]. To run it, setup a
|
|
54
|
+
virtual environment as shown above, then run:
|
|
55
|
+
```shell
|
|
56
|
+
pytest
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
In addition to the test results, a test coverage report will also be generated
|
|
60
|
+
using [pytest-cov][pytest-cov]
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
## Custom Annotations
|
|
64
|
+
|
|
65
|
+
`shacl2code` supports a number of custom annotations that can be specified in a
|
|
66
|
+
SHACL model to give hints about the generated code. All of these annotations
|
|
67
|
+
live in the `https://jpewdev.github.io/shacl2code/schema#` namespace, and
|
|
68
|
+
commonly are given the `sh-to-code` prefix to make it easier to reference them.
|
|
69
|
+
For example, in Turtle one would add the prefix mapping:
|
|
70
|
+
|
|
71
|
+
```ttl
|
|
72
|
+
@prefix sh-to-code: <https://jpewdev.github.io/shacl2code/schema#> .
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### ID Property Name
|
|
76
|
+
|
|
77
|
+
The `idPropertyName` annotation allows a class to specify what the name of the
|
|
78
|
+
"property" that specifies the RDF subject of an object is for serializations
|
|
79
|
+
that support it. For example, in JSON-LD, the `@id` property indicates the
|
|
80
|
+
subject in RDF. If you wanted to alias the `@id` property to another name, the
|
|
81
|
+
`idPropertyName` annotation will let you do this. For example, the following
|
|
82
|
+
turtle will use `MyId` instead of `@id` when writing JSON-LD bindings:
|
|
83
|
+
|
|
84
|
+
```ttl
|
|
85
|
+
<MyClass> a owl:Class, sh:NodeShape ;
|
|
86
|
+
sh-to-code:idPropertyName "MyId"
|
|
87
|
+
.
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
When doing this, the class would then look like this in JSON-LD:
|
|
91
|
+
```json
|
|
92
|
+
{
|
|
93
|
+
"@type": "MyClass",
|
|
94
|
+
"MyId": "http://example.com/id"
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
The `idProperyName` annotation is inherited by derived classes, so for example
|
|
99
|
+
any class that derived from `MyClass` would also use `MyId` as the subject
|
|
100
|
+
property.
|
|
101
|
+
|
|
102
|
+
**Note:** This only specifies what the name of the field should be in generated
|
|
103
|
+
bindings and has no bearing on how an RDF parser would interpret the property.
|
|
104
|
+
In order to still be parsed by RDF, you would also need context file that maps
|
|
105
|
+
`MyId` to `@id`, for example:
|
|
106
|
+
|
|
107
|
+
```json
|
|
108
|
+
{
|
|
109
|
+
"@context": {
|
|
110
|
+
"MyId": "@id"
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
`shacl2code` doesn't do this for you, nor does it validate that you have done
|
|
116
|
+
it.
|
|
117
|
+
|
|
118
|
+
### Extensible Classes
|
|
119
|
+
|
|
120
|
+
Most bindings generated from `shacl2code` are "closed" in that they do not
|
|
121
|
+
allow extra properties to be added to object outside of what is specified in
|
|
122
|
+
model. This ensures that field name typos and other unintended properties are
|
|
123
|
+
not added to an object. However, in some cases a class may be specifically
|
|
124
|
+
intended to be extended such that arbitrary fields can be added to it, which
|
|
125
|
+
can be done using the `isExtensible` property. This is a boolean property that
|
|
126
|
+
indicates if a class can be extended, and defaults to `false`. For example, the
|
|
127
|
+
following turtle will declare a class as extensible:
|
|
128
|
+
|
|
129
|
+
```ttl
|
|
130
|
+
|
|
131
|
+
<MyClass> a owl:Class, sh:NodeShape ;
|
|
132
|
+
sh-to-code:isExtensible true
|
|
133
|
+
.
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
The `isExtensible` property is _not_ inherited by derived classes, meaning it
|
|
137
|
+
is possible to have a class derived from `MyClass` which is itself not
|
|
138
|
+
extensible.
|
|
139
|
+
|
|
140
|
+
The mechanism for dealing with extensible classes will vary between the
|
|
141
|
+
different bindings, but in general it means that they will not be very picky
|
|
142
|
+
about object types and properties in any location where an extensible class is
|
|
143
|
+
allowed.
|
|
144
|
+
|
|
145
|
+
**Note**: You may want to be careful about where and how many extensible
|
|
146
|
+
classes are allowed in your model. If there are too many and they are allowed
|
|
147
|
+
anywhere, it may mean that typos in object types (e.g. `@type` in JSON-LD) are
|
|
148
|
+
not caught by validation as they will have to be assumed to be a derived class
|
|
149
|
+
from an extensible type.
|
|
150
|
+
|
|
151
|
+
### Abstract Classes
|
|
152
|
+
|
|
153
|
+
By default, classes generated by `shacl2code` are all instantiable (i.e. they
|
|
154
|
+
can be created). In some instances, it may be desirable to declare a class as
|
|
155
|
+
abstract (meaning that it cannot be instantiated, but non-abstract derived
|
|
156
|
+
classes can). This can be done with the boolean `isAbstract` property. For
|
|
157
|
+
example, the following turtle will declare a class as abstract:
|
|
158
|
+
|
|
159
|
+
```ttl
|
|
160
|
+
|
|
161
|
+
<MyClass> a owl:Class, sh:NodeShape ;
|
|
162
|
+
sh-to-code:isAbstract true
|
|
163
|
+
.
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
The `isAbstract` property is _not_ inherited by derived classes, so any derived
|
|
167
|
+
classes are automatically concrete unless they indicate otherwise.
|
|
168
|
+
|
|
169
|
+
Note that for compatibility reasons, it is also possible to define a class as
|
|
170
|
+
abstract by declaring it to be of type: `http://spdx.invalid./AbstractClass`,
|
|
171
|
+
but this is not preferred.
|
|
172
|
+
|
|
173
|
+
[pytest]: https://www.pytest.org
|
|
174
|
+
[pytest-cov]: https://pytest-cov.readthedocs.io/en/latest/
|
|
@@ -31,15 +31,15 @@ class BasicJinjaRender(object):
|
|
|
31
31
|
"""
|
|
32
32
|
Common Jinja Template Renderer
|
|
33
33
|
|
|
34
|
-
Renderers that only use a single Jinja file can derive from this class.
|
|
35
|
-
|
|
36
|
-
template file to use. For example:
|
|
34
|
+
Renderers that only use a single Jinja file can derive from this class. For
|
|
35
|
+
example:
|
|
37
36
|
|
|
38
37
|
@language("my-lang")
|
|
39
38
|
class MyRendered(BasicJinjaRenderer):
|
|
40
39
|
HELP = "Generates my-lang bindings"
|
|
41
|
-
TEMPLATE = "my-lang.j2"
|
|
42
40
|
|
|
41
|
+
def __init__(self, args):
|
|
42
|
+
super().__init__(args, PATH / TO / TEMPLATE)
|
|
43
43
|
"""
|
|
44
44
|
|
|
45
45
|
def __init__(self, args, template):
|
|
@@ -62,22 +62,31 @@ class BasicJinjaRender(object):
|
|
|
62
62
|
def get_extra_env(self):
|
|
63
63
|
return {}
|
|
64
64
|
|
|
65
|
-
def
|
|
65
|
+
def render(self, template, output, *, extra_env={}, render_args={}):
|
|
66
66
|
def abort_helper(msg):
|
|
67
67
|
raise TemplateRuntimeError(msg)
|
|
68
68
|
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
env = Environment(loader=FileSystemLoader([template.parent, THIS_DIR.parent]))
|
|
70
|
+
for k, v in extra_env.items():
|
|
71
|
+
env.globals[k] = v
|
|
72
|
+
env.globals["abort"] = abort_helper
|
|
73
|
+
env.globals["SHACL2CODE"] = SHACL2CODE
|
|
74
|
+
env.globals["SH"] = SH
|
|
75
|
+
template = env.get_template(template.name)
|
|
71
76
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
return result
|
|
77
|
+
render = template.render(
|
|
78
|
+
disclaimer=f"This file was automatically generated by {os.path.basename(sys.argv[0])}. DO NOT MANUALLY MODIFY IT",
|
|
79
|
+
**render_args,
|
|
80
|
+
)
|
|
77
81
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
82
|
+
output.write(render)
|
|
83
|
+
if not render[-1] == "\n":
|
|
84
|
+
output.write("\n")
|
|
85
|
+
|
|
86
|
+
def output(self, model):
|
|
87
|
+
"""
|
|
88
|
+
Render the provided model
|
|
89
|
+
"""
|
|
81
90
|
|
|
82
91
|
class ObjectList(object):
|
|
83
92
|
def __init__(self, objs):
|
|
@@ -92,29 +101,44 @@ class BasicJinjaRender(object):
|
|
|
92
101
|
return o
|
|
93
102
|
raise KeyError(f"Object with ID {_id} not found")
|
|
94
103
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
104
|
+
def get_all_derived(cls):
|
|
105
|
+
nonlocal classes
|
|
106
|
+
|
|
107
|
+
def _recurse(cls):
|
|
108
|
+
result = set(cls.derived_ids)
|
|
109
|
+
for r in cls.derived_ids:
|
|
110
|
+
result |= _recurse(classes.get(r))
|
|
111
|
+
return result
|
|
112
|
+
|
|
113
|
+
d = list(_recurse(cls))
|
|
114
|
+
d.sort()
|
|
115
|
+
return d
|
|
105
116
|
|
|
106
117
|
classes = ObjectList(model.classes)
|
|
118
|
+
concrete_classes = ObjectList(
|
|
119
|
+
list(c for c in model.classes if not c.is_abstract)
|
|
120
|
+
)
|
|
121
|
+
abstract_classes = ObjectList(list(c for c in model.classes if c.is_abstract))
|
|
107
122
|
enums = ObjectList(model.enums)
|
|
108
123
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
124
|
+
render_args = {
|
|
125
|
+
"classes": classes,
|
|
126
|
+
"concrete_classes": concrete_classes,
|
|
127
|
+
"abstract_classes": abstract_classes,
|
|
128
|
+
"enums": enums,
|
|
129
|
+
"context": model.context,
|
|
114
130
|
**self.get_additional_render_args(),
|
|
115
|
-
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
env = {
|
|
134
|
+
"get_all_derived": get_all_derived,
|
|
135
|
+
**self.get_extra_env(),
|
|
136
|
+
}
|
|
116
137
|
|
|
117
138
|
with self.__output.open() as f:
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
f
|
|
139
|
+
self.render(
|
|
140
|
+
self.__template,
|
|
141
|
+
f,
|
|
142
|
+
extra_env=env,
|
|
143
|
+
render_args=render_args,
|
|
144
|
+
)
|
|
@@ -10,8 +10,8 @@ import re
|
|
|
10
10
|
import keyword
|
|
11
11
|
|
|
12
12
|
|
|
13
|
-
def varname(name):
|
|
14
|
-
name = str(name).replace("@", "_")
|
|
13
|
+
def varname(*name):
|
|
14
|
+
name = str("_".join(name)).replace("@", "_")
|
|
15
15
|
name = re.sub(r"[^a-zA-Z0-9_]", "", name)
|
|
16
16
|
while keyword.iskeyword(name):
|
|
17
17
|
name = name + "_"
|