mirror of
				https://github.com/RGBCube/GitHubWrapper
				synced 2025-10-30 21:42:45 +00:00 
			
		
		
		
	Add tools/unschema: This generates TypedDicts from JSON response schemas
This commit is contained in:
		
							parent
							
								
									ce6d3c5b5b
								
							
						
					
					
						commit
						7c9d7d4862
					
				
					 3 changed files with 183 additions and 0 deletions
				
			
		
							
								
								
									
										1
									
								
								tools/unschema/__init__.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								tools/unschema/__init__.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1 @@ | |||
| from .parser import generate | ||||
							
								
								
									
										50
									
								
								tools/unschema/__main__.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								tools/unschema/__main__.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,50 @@ | |||
| import json | ||||
| import time | ||||
| from argparse import ArgumentParser | ||||
| 
 | ||||
| from .parser import generate | ||||
| 
 | ||||
| parser = ArgumentParser(description="Generate TypedDicts from a json schema.") | ||||
| parser.add_argument("-f", "--from", required=True, help="The json schema to generate from.") | ||||
| parser.add_argument("-t", "--to", help="The file to write the TypedDicts to.") | ||||
| parser.add_argument( | ||||
|     "-ne", "--no-examples", action="store_true", help="If given, examples wont be added." | ||||
| ) | ||||
| parser.add_argument( | ||||
|     "-nf", "--no-formats", action="store_true", help="If given, formats wont be added." | ||||
| ) | ||||
| parser.add_argument( | ||||
|     "-p", | ||||
|     "--print", | ||||
|     action="store_true", | ||||
|     help="If given, the result will be printed instead of being writing to a file", | ||||
| ) | ||||
| args = parser.parse_args() | ||||
| 
 | ||||
| with open(args.__getattribute__("from")) as g: | ||||
|     schema = json.load(g) | ||||
| 
 | ||||
| start = time.monotonic() | ||||
| text = f""" | ||||
| from __future__ import annotations | ||||
| 
 | ||||
| from typing import List, Optional, TypedDict, Union, TYPE_CHECKING | ||||
| 
 | ||||
| if TYPE_CHECKING: | ||||
|     from typing_extensions import NotRequired{generate(schema, no_examples=args.no_examples, no_formats=args.no_formats)}  # Rename this to your liking. | ||||
| """[ | ||||
|     1: | ||||
| ] | ||||
| 
 | ||||
| if args.print: | ||||
|     print(text) | ||||
| else: | ||||
|     if not args.to: | ||||
|         raise ValueError("-t/--to is required when writing to a file.") | ||||
| 
 | ||||
|     with open(args.to, "w") as f: | ||||
|         f.write(text) | ||||
| 
 | ||||
| end = time.monotonic() - start | ||||
| 
 | ||||
| print(f"Success! Finished in {end*1000:.3} milliseconds.") | ||||
							
								
								
									
										132
									
								
								tools/unschema/parser.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										132
									
								
								tools/unschema/parser.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,132 @@ | |||
| from __future__ import annotations | ||||
| 
 | ||||
| from typing import Iterable, Tuple | ||||
| 
 | ||||
| types = { | ||||
|     "string": "str", | ||||
|     "integer": "int", | ||||
|     "number": "float", | ||||
|     "boolean": "bool", | ||||
|     "object": "dict", | ||||
|     "array": "list", | ||||
|     "null": "None", | ||||
| } | ||||
| 
 | ||||
| untypes = {v: k for k, v in types.items()} | ||||
| 
 | ||||
| 
 | ||||
| def unspace(s: str) -> str: | ||||
|     return s.replace(" ", "") | ||||
| 
 | ||||
| 
 | ||||
| def mklist(s: Iterable) -> str: | ||||
|     return f"[{', '.join(s)}]" | ||||
| 
 | ||||
| 
 | ||||
| def generate( | ||||
|     _schema: dict, | ||||
|     /, | ||||
|     *, | ||||
|     no_examples: bool = False, | ||||
|     no_formats: bool = False, | ||||
|     title: str = "GeneratedObject", | ||||
| ) -> str: | ||||
|     def inner_generate( | ||||
|         schema: dict, | ||||
|         /, | ||||
|         *, | ||||
|         _no_examples: bool = no_examples, | ||||
|         _no_formats: bool = no_formats, | ||||
|         _title: str = title, | ||||
|     ) -> Tuple[str, str]:  # sourcery skip: low-code-quality | ||||
| 
 | ||||
|         # GeneratedObject = Union[..., ..., ...] | ||||
|         if objects := schema.get("oneOf"): | ||||
|             text = [] | ||||
|             union_items = [] | ||||
| 
 | ||||
|             for item_schema in objects: | ||||
|                 generated_text, union_item = generate(item_schema, title=_title) | ||||
| 
 | ||||
|                 text.append(generated_text) | ||||
|                 union_items.append(union_item) | ||||
| 
 | ||||
|             text.append(f"{_title} = Union{mklist(union_items)}") | ||||
|             return "\n\n".join(_title), _title | ||||
|         del objects | ||||
| 
 | ||||
|         # class GeneratedObject(TypedDict): | ||||
|         if (object_type := schema["type"]) == untypes["dict"]: | ||||
|             _title = unspace(schema["title"]) | ||||
| 
 | ||||
|             dependencies = [] | ||||
|             current = [f'class {_title}(TypedDict):\n    """{schema["description"]}"""'] | ||||
| 
 | ||||
|             for key, value in schema["properties"].items(): | ||||
|                 if isinstance(value_type := value["type"], str): | ||||
|                     param_type = types[value_type] | ||||
| 
 | ||||
|                 elif isinstance(value_type, list): | ||||
|                     combiner = "Union" | ||||
|                     contents = [] | ||||
| 
 | ||||
|                     for type in value_type: | ||||
|                         if type == untypes["None"]: | ||||
|                             combiner = "Optional" | ||||
|                         else: | ||||
|                             contents.append(types[type]) | ||||
| 
 | ||||
|                     param_type = f"{combiner}{mklist(contents)}" | ||||
| 
 | ||||
|                 elif isinstance(value_type, dict): | ||||
|                     text, target = inner_generate( | ||||
|                         value_type, _title=key, _no_examples=_no_examples, _no_formats=_no_formats | ||||
|                     ) | ||||
|                     dependencies.append(text) | ||||
|                     param_type = target | ||||
| 
 | ||||
|                 else: | ||||
|                     param_type = f"Unknown[{value_type}]" | ||||
| 
 | ||||
|                 if key not in schema["required"]: | ||||
|                     param_type = f"NotRequired[{param_type}]" | ||||
| 
 | ||||
|                 eg = ( | ||||
|                     "" | ||||
|                     if no_examples | ||||
|                     else f"    # example: {mklist(str(eg) for eg in egs)[1:-1]}\n" | ||||
|                     if (egs := value.get("examples")) | ||||
|                     else "" | ||||
|                 ) | ||||
| 
 | ||||
|                 fmt = ( | ||||
|                     "" | ||||
|                     if no_formats | ||||
|                     else f"    # format: {fmt}\n" | ||||
|                     if (fmt := value.get("format")) | ||||
|                     else "" | ||||
|                 ) | ||||
| 
 | ||||
|                 sep = "\n" if eg or fmt else "" | ||||
| 
 | ||||
|                 com = "# " if param_type.startswith("Unknown") else "" | ||||
| 
 | ||||
|                 current.append(f"{sep}{eg}{fmt}    {com}{key}: {param_type}") | ||||
| 
 | ||||
|             dependencies.append("\n".join(current)) | ||||
|             result = "\n\n".join(dependencies[:-1]) + "\n\n\n" + dependencies[-1] | ||||
| 
 | ||||
|             return result, _title | ||||
| 
 | ||||
|         # GeneratedObject = List[...] | ||||
|         elif object_type == untypes["list"]: | ||||
|             generated, target = inner_generate( | ||||
|                 schema["items"], _title=_title, _no_examples=_no_examples, _no_formats=_no_formats | ||||
|             ) | ||||
|             return f"{generated}\n\n\n{_title} = List[{target}]", _title | ||||
| 
 | ||||
|         else: | ||||
|             print("Unknown/Unimplemented Object Type:", object_type) | ||||
|             return "", "" | ||||
| 
 | ||||
|     return inner_generate(_schema)[0] | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 RGBCube
						RGBCube