Skip to content

Commit 982bd2e

Browse files
committed
Add docs;
Change default yaml parser; Handle non-str keys in dicts
1 parent ada5f67 commit 982bd2e

File tree

5 files changed

+157
-20
lines changed

5 files changed

+157
-20
lines changed

README.md

+147-15
Original file line numberDiff line numberDiff line change
@@ -277,15 +277,138 @@ class Response:
277277

278278
@dataclass
279279
class Definition_Schema:
280-
type_: str
281-
required: Optional[List[str]] = field(default_factory=list)
282-
properties: Optional[Dict[str, Union['Property', 'Property_2E']]] = field(default_factory=dict)
283-
ref: Optional[str] = None
280+
type_: str
281+
required: Optional[List[str]] = field(default_factory=list)
282+
properties: Optional[Dict[str, Union['Property', 'Property_2E']]] = field(default_factory=dict)
283+
ref: Optional[str] = None
284284
```
285285

286286
</p>
287287
</details>
288288

289+
### Github-actions config files
290+
291+
<details><summary>----- Show -----</summary>
292+
<p>
293+
294+
Github-actions model based on files from [starter-workflows](https://github.com/actions/starter-workflows/tree/main/ci)
295+
296+
```
297+
json2models -m Actions "./starter-workflows/ci/*.yml" -s flat -f pydantic -i yaml --dkf env with jobs
298+
```
299+
300+
```python
301+
r"""
302+
generated by json2python-models v0.2.3 at Tue Jul 13 19:52:43 2021
303+
command: /opt/projects/json2python-models/venv/bin/json2models -m Actions ./starter-workflows/ci/*.yml -s flat -f pydantic -i yaml --dkf env with jobs
304+
"""
305+
from pydantic import BaseModel, Field
306+
from typing import Dict, List, Optional, Union
307+
from typing_extensions import Literal
308+
309+
310+
class Actions(BaseModel):
311+
on: Union['On', List[Literal["push"]]]
312+
jobs: Dict[str, 'Job']
313+
name: Optional[str] = None
314+
env: Optional[Dict[str, Union[int, str]]] = {}
315+
316+
317+
class On(BaseModel):
318+
push: Optional['Push'] = None
319+
pull_request: Optional['PullRequest'] = None
320+
release: Optional['Release'] = None
321+
schedule: Optional[List['Schedule']] = []
322+
workflow_dispatch: Optional[None] = None
323+
324+
325+
class Push(BaseModel):
326+
branches: List[Literal["$default-branch"]]
327+
tags: Optional[List[Literal["v*.*.*"]]] = []
328+
329+
330+
class PullRequest(BaseModel):
331+
branches: List[Literal["$default-branch"]]
332+
333+
334+
class Release(BaseModel):
335+
types: List[Literal["created", "published"]]
336+
337+
338+
class Schedule(BaseModel):
339+
cron: Literal["$cron-daily"]
340+
341+
342+
class Job(BaseModel):
343+
runson: Literal[
344+
"${{ matrix.os }}", "macOS-latest", "macos-latest", "ubuntu-18.04", "ubuntu-latest", "windows-latest"] = Field(
345+
..., alias="runs-on")
346+
steps: List['Step']
347+
name: Optional[str] = None
348+
environment: Optional[Literal["production"]] = None
349+
outputs: Optional['Output'] = None
350+
container: Optional['Container'] = None
351+
needs: Optional[Literal["build"]] = None
352+
permissions: Optional['Permission'] = None
353+
strategy: Optional['Strategy'] = None
354+
defaults: Optional['Default'] = None
355+
env: Optional[Dict[str, str]] = {}
356+
357+
358+
class Step(BaseModel):
359+
uses: Optional[str] = None
360+
name: Optional[str] = None
361+
with_: Optional[Dict[str, Union[bool, float, str]]] = Field({}, alias="with")
362+
run: Optional[str] = None
363+
env: Optional[Dict[str, str]] = {}
364+
workingdirectory: Optional[str] = Field(None, alias="working-directory")
365+
id_: Optional[Literal[
366+
"build-image", "composer-cache", "deploy-and-expose", "image-build", "login-ecr", "meta", "push-to-registry", "task-def"]] = Field(
367+
None, alias="id")
368+
if_: Optional[str] = Field(None, alias="if")
369+
shell: Optional[Literal["Rscript {0}"]] = None
370+
371+
372+
class Output(BaseModel):
373+
route: str = Field(..., alias="ROUTE")
374+
selector: str = Field(..., alias="SELECTOR")
375+
376+
377+
class Container(BaseModel):
378+
image: Literal["crystallang/crystal", "erlang:22.0.7"]
379+
380+
381+
class Permission(BaseModel):
382+
contents: Literal["read"]
383+
packages: Literal["write"]
384+
385+
386+
class Strategy(BaseModel):
387+
matrix: Optional['Matrix'] = None
388+
maxparallel: Optional[int] = Field(None, alias="max-parallel")
389+
failfast: Optional[bool] = Field(None, alias="fail-fast")
390+
391+
392+
class Matrix(BaseModel):
393+
rversion: Optional[List[float]] = Field([], alias="r-version")
394+
pythonversion: Optional[List[float]] = Field([], alias="python-version")
395+
deno: Optional[List[Literal["canary", "v1.x"]]] = []
396+
os: Optional[List[Literal["macOS-latest", "ubuntu-latest", "windows-latest"]]] = []
397+
rubyversion: Optional[List[float]] = Field([], alias="ruby-version")
398+
nodeversion: Optional[List[Literal["12.x", "14.x", "16.x"]]] = Field([], alias="node-version")
399+
configuration: Optional[List[Literal["Debug", "Release"]]] = []
400+
401+
402+
class Default(BaseModel):
403+
run: 'Run'
404+
405+
406+
class Run(BaseModel):
407+
shell: Literal["bash"]
408+
```
409+
410+
</p></details>
411+
289412
## Installation
290413

291414
| **Be ware**: this project supports only `python3.7` and higher. |
@@ -315,24 +438,33 @@ json2models -m Car car_*.json -f attrs > car.py
315438

316439
Arguments:
317440
* `-h`, `--help` - Show help message and exit
318-
319-
* `-m`, `--model` - Model name and its JSON data as path or unix-like path pattern. `*`, `**` or `?` patterns symbols are supported.
441+
442+
* `-m`, `--model` - Model name and its JSON data as path or unix-like path pattern. `*`, `**` or `?` patterns symbols
443+
are supported.
320444
* **Format**: `-m <Model name> [<JSON files> ...]`
321445
* **Example**: `-m Car audi.json reno.json` or `-m Car audi.json -m Car reno.json` (results will be the same)
322-
323-
* `-l`, `--list` - Like `-m` but given json file should contain list of model data (dataset).
324-
If this file contains dict with nested list than you can pass `<JSON key>` to lookup.
325-
Deep lookups are supported by dot-separated path. If no lookup needed pass `-` as `<JSON key>`.
446+
447+
* `-l`, `--list` - Like `-m` but given json file should contain list of model data (dataset). If this file contains dict
448+
with nested list than you can pass `<JSON key>` to lookup. Deep lookups are supported by dot-separated path. If no
449+
lookup needed pass `-` as `<JSON key>`.
326450
* **Format**: `-l <Model name> <JSON key> <JSON file>`
327451
* **Example**: `-l Car - cars.json -l Person fetch_results.items.persons result.json`
328-
* **Note**: Models names under this arguments should be unique.
329-
452+
* **Note**: Models names under these arguments should be unique.
453+
454+
* `-i`, `--input-format` - Input file format (parser). Default is JSON parser. Yaml parser requires PyYaml or
455+
ruamel.yaml to be installed. Ini parser uses
456+
builtin [configparser](https://docs.python.org/3/library/configparser.html). To implement new one - add new method
457+
to `cli.FileLoaders` (and create pull request :) )
458+
* **Format**: `-i {json, yaml, ini}`
459+
* **Example**: `-i yaml`
460+
* **Default**: `-i json`
461+
330462
* `-o`, `--output` - Output file
331463
* **Format**: `-o <FILE>`
332464
* **Example**: `-o car_model.py`
333-
334-
* `-f`, `--framework` - Model framework for which python code is generated.
335-
`base` (default) mean no framework so code will be generated without any decorators and additional meta-data.
465+
466+
* `-f`, `--framework` - Model framework for which python code is generated.
467+
`base` (default) mean no framework so code will be generated without any decorators and additional meta-data.
336468
* **Format**: `-f {base, pydantic, attrs, dataclasses, custom}`
337469
* **Example**: `-f pydantic`
338470
* **Default**: `-f base`

json_to_models/cli.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@
1212
from typing import Any, Callable, Dict, Generator, Iterable, List, Tuple, Type, Union
1313

1414
try:
15-
import yaml
15+
import ruamel.yaml as yaml
1616
except ImportError:
1717
try:
18-
import ruamel.yaml as yaml
18+
import yaml
1919
except ImportError:
2020
yaml = None
2121

@@ -268,7 +268,7 @@ def _create_argparser(cls) -> argparse.ArgumentParser:
268268
)
269269
parser.add_argument(
270270
"-i", "--input-format",
271-
metavar="FORMAT", default="json",
271+
default="json",
272272
choices=['json', 'yaml', 'ini'],
273273
help="Input files parser ('PyYaml' is required to parse yaml files)\n\n"
274274
)

json_to_models/generator.py

+5
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,11 @@ def _convert(self, data: dict):
5656
"""
5757
fields = dict()
5858
for key, value in data.items():
59+
if not isinstance(key, str):
60+
raise TypeError(f'You probably using some not JSON-compatible parser and have some {type(key)} as dict key. '
61+
f'This is not supported.\n'
62+
f'Context: {data}\n'
63+
f'(If you parsing yaml try to replace PyYaml with ruamel.yaml)')
5964
convert_dict = key not in self.dict_keys_fields
6065
fields[key] = self._detect_type(value, convert_dict)
6166
return fields

setup.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,6 @@ def run_tests(self):
5050
},
5151
install_requires=required,
5252
cmdclass={"test": PyTest},
53-
tests_require=["pytest>=4.4.0", "pytest-xdist", "requests", "attrs", "pydantic>=1.3", "PyYaml"],
53+
tests_require=["pytest>=4.4.0", "pytest-xdist", "requests", "attrs", "pydantic>=1.3", "ruamel.yaml"],
5454
data_files=[('', ['requirements.txt', 'pytest.ini', '.coveragerc', 'LICENSE', 'README.md', 'CHANGELOG.md'])]
5555
)

test/test_generator/test_detect_type.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
pytest.param("1.0", FloatString, id="float_str"),
2929
pytest.param("true", BooleanString, id="bool_str"),
3030
pytest.param({"test_dict_field_a": 1, "test_dict_field_b": "a"}, DDict(DUnion(int, StringLiteral({"a"}))), id="simple_dict"),
31-
pytest.param({}, DDict(Unknown))
31+
pytest.param({}, DDict(Unknown), id="empty_dict")
3232
]
3333

3434
test_dict = {param.id: param.values[0] for param in test_data}

0 commit comments

Comments
 (0)