1919#
2020# I want to avoid setattr and string repr of methods so IDEs have a chance. OTOH, IDEs don't give a crap about the exception wrapper; I'm not changing the signature as far as Python is concerned.
2121
22+ from collections import defaultdict
2223import json
24+ from pathlib import Path
2325from subprocess import check_output
2426import textwrap
2527from typing import Any , Iterable , Self
2628from types import NoneType
2729
30+
2831WIT_DIR = "wit"
32+ PACKAGE = "fastly:compute@0.0.0-prerelease.0"
2933
3034
3135class DocsHaver :
@@ -66,7 +70,7 @@ class Type(DocsHaver):
6670 """
6771
6872 @classmethod
69- def from_id (cls , type_id : int | str | None , types_json : list [dict ]) -> Self :
73+ def from_id (cls , type_id : int | str | None , wit_json : list [dict ]) -> Self :
7074 """Construct a type of the given index in the WIT's "types" key.
7175
7276 If that type is an alias (which is the case when referencing a type from
@@ -78,30 +82,30 @@ def from_id(cls, type_id: int | str | None, types_json: list[dict]) -> Self:
7882 while True :
7983 if isinstance (type_id , str ):
8084 # It's a primitive.
81- return cls (type_id , type_id , types_json )
85+ return cls (type_id , type_id , wit_json )
8286 elif isinstance (type_id , NoneType ):
8387 return NullType ()
8488
8589 # It's an int. Chase that down, including following type aliases.
86- current_type = types_json [type_id ]
90+ current_type = wit_json [ "types" ] [type_id ]
8791 next_type = current_type ["kind" ].get ("type" )
8892 if next_type is None :
8993 kind = only (current_type ["kind" ].keys ())
9094 class_ = KINDS_TO_CLASSES .get (kind , cls )
91- return class_ (type_id , current_type , types_json )
95+ return class_ (type_id , current_type , wit_json )
9296 else :
9397 # It's a pointer to a different tyoe.
9498 type_id = next_type
9599
96- def __init__ (self , id : int | str , type_ : dict | str , types_json : list [dict ]):
100+ def __init__ (self , id : int | str , type_ : dict | str , wit_json : list [dict ]):
97101 """Private constructor. Use from_id() instead.
98102
99103 Type instances compare and hash based on their IDs: their positions in
100104 the WIT's type array. Only non-alias type IDs occur in instances.
101105 """
102106 self ._id : int | str = id
103107 self ._type : str | dict [str ] = type_
104- self ._types = types_json
108+ self ._wit = wit_json
105109
106110 def name (self ) -> str :
107111 """Return the name of this type, in usual WIT kebab case."""
@@ -122,13 +126,17 @@ def __repr__(self):
122126 def has_cases (self ):
123127 return False
124128
129+ def interface (self ) -> "Interface" :
130+ """Return the interface where this type is defined."""
131+ return Interface (self ._wit ["interfaces" ][self ._type ["owner" ]["interface" ]], self ._wit )
132+
125133
126134class Result (Type ):
127135 """A WIT ``result``"""
128136
129137 def error_type (self ):
130138 """Return the type of my error case."""
131- return self .from_id (self ._type ["kind" ]["result" ]["err" ], self ._types )
139+ return self .from_id (self ._type ["kind" ]["result" ]["err" ], self ._wit )
132140
133141
134142class NullType (Type ):
@@ -174,9 +182,9 @@ def cases(self):
174182
175183
176184class Function :
177- def __init__ (self , function_json : dict , types_json : list [dict ]):
185+ def __init__ (self , function_json : dict , wit_json : list [dict ]):
178186 self ._function = function_json
179- self ._types = types_json
187+ self ._wit = wit_json
180188
181189 def error_type_of_returned_result (self ) -> Type | None :
182190 """If this Function returns a single ``result`` type, return the type of its error case.
@@ -188,30 +196,32 @@ def error_type_of_returned_result(self) -> Type | None:
188196 if num_returns > 1 :
189197 raise NotImplementedError ("Multi-value return isn't supported yet." )
190198 if num_returns == 1 :
191- return_type = Type .from_id (returns [0 ]["type" ], self ._types )
199+ return_type = Type .from_id (returns [0 ]["type" ], self ._wit )
192200 if isinstance (return_type , Result ):
193201 return return_type .error_type ()
194202
195203
196204class Interface :
197- def __init__ (self , interface_json : dict [str , Any ], types_json : list [dict ]):
205+ def __init__ (self , interface_json : dict [str , Any ], wit_json : list [dict ]):
198206 self ._interface = interface_json
199- self ._types = types_json
207+ self ._wit = wit_json
208+
209+ def name (self ) -> str :
210+ return self ._interface ["name" ]
200211
201212 def functions (self ) -> Iterable [Function ]:
202213 for function in self ._interface ["functions" ].values ():
203- yield Function (function , self ._types )
214+ yield Function (function , self ._wit )
204215
205216
206217class Package :
207- def __init__ (self , package_json : dict , interfaces_json : list [ dict ], types_json : list [dict ]):
218+ def __init__ (self , package_json : dict , wit_json : list [dict ]):
208219 self ._package = package_json
209- self ._interfaces = interfaces_json
210- self ._types = types_json
220+ self ._wit = wit_json
211221
212222 def interfaces (self ) -> Iterable [Interface ]:
213223 for interface_num in self ._package ["interfaces" ].values ():
214- yield Interface (self ._interfaces [ interface_num ], self ._types )
224+ yield Interface (self ._wit [ "interfaces" ][ interface_num ], self ._wit )
215225
216226
217227class Wit :
@@ -231,7 +241,7 @@ def __init__(self, wit_json: dict[str, list[dict]]):
231241
232242 :arg wit_json: The loaded JSON out of ``wasm-tools component wit wit/ --json``
233243 """
234- self ._packages : dict [str , Package ] = {p ["name" ]: Package (p , wit_json [ "interfaces" ], wit_json [ "types" ] ) for p in wit_json ["packages" ]}
244+ self ._packages : dict [str , Package ] = {p ["name" ]: Package (p , wit_json ) for p in wit_json ["packages" ]}
235245
236246 def package (self , name : str ) -> Package :
237247 """Return a package of the given name, e.g.
@@ -240,10 +250,15 @@ def package(self, name: str) -> Package:
240250
241251
242252def upper_camel (s : str ):
243- """Convert lower-kebab case to upper camel case ."""
253+ """Convert lower-kebab case to UpperCamelCase ."""
244254 return "" .join (word .capitalize () for word in s .split ('-' ))
245255
246256
257+ def lower_snake (s : str ):
258+ """Convert lower-kebab case to lower_snake_case."""
259+ return s .replace ("-" , "_" )
260+
261+
247262def indent (s : str ):
248263 """Indent as for a docstring.
249264
@@ -253,28 +268,60 @@ def indent(s: str):
253268 return textwrap .indent (s , " " ).strip ()
254269
255270
256- def generate_exception_classes (error_types : Iterable [Type ]):
271+ def exception_code_tree (error_types : Iterable [Type ]) -> dict [ str , dict [ str , str ]] :
257272 """Generate Python exception classes we can map variant and enum cases to.
258273
259274 Inherit the names and docstrings from the WIT. Create a common superclass
260275 for each type so you can catch the whole smear if you like.
276+
277+ :return: A dict of package names pointing to module names pointing to
278+ contained code. For example, acl (from the interface name) -> acl_error
279+ (from the enum name) -> class AclError(FastlyError)...
261280 """
262- ret = ""
281+ # interface name -> module name -> code chunks:
282+ code = defaultdict (lambda : defaultdict (str ))
283+
263284 for error_type in error_types :
264- error_class_name = upper_camel (error_type .name ())
265- # Common superclass for exceptions based on the enum or variant's
266- # members. Also functions as the raised exception itself for records.
267- ret += (f'''class { error_class_name } (FastlyError):\n '''
268- f''' """{ error_type .docstring_or_pass ()} """\n \n \n ''' )
285+ package = lower_snake (error_type .interface ().name ())
269286 if error_type .has_cases (): # It's an enum or variant.
270- for case in error_type .cases ():
271- ret += ( f'''class { upper_camel ( case . name ()) } ( { error_class_name } ): \n '''
272- f''' """ { case . docstring_or_pass () } """ \n \n \n ''' )
273- elif not isinstance ( error_type , Record ) :
287+ module = lower_snake ( error_type .name ()) + ".py"
288+ elif isinstance ( error_type , Record ):
289+ module = "__init__.py"
290+ else :
274291 raise NotImplementedError (
275292 "Only variants, enums, and records are currently handled as "
276293 "``result`` error types. Looks like it's time to support others!" )
277- return ret
294+
295+ # Create package's empty __init__.py if not already there:
296+ code [package ]["__init__.py" ]
297+
298+ error_class_name = upper_camel (error_type .name ())
299+ # Common superclass for exceptions based on the enum or variant's
300+ # members. Or the raised exception itself for records.
301+ code [package ][module ] += (
302+ f'''class { error_class_name } (FastlyError):\n '''
303+ f''' """{ error_type .docstring_or_pass ()} """\n \n \n ''' )
304+ if error_type .has_cases ():
305+ # Insert enum or variant cases.
306+ for case in error_type .cases ():
307+ code [package ][module ] += (
308+ f'''class { upper_camel (case .name ())} ({ error_class_name } ):\n '''
309+ f''' """{ case .docstring_or_pass ()} """\n \n \n ''' )
310+ return code
311+
312+
313+ def write_files (tree : dict [str , dict [str , str ]], base_folder : Path ):
314+ """Create filesystem artifacts mirroring a nested dict representing folders,
315+ then files, then file contents.
316+
317+ Overwrite files that are mentioned in ``tree``, but don't delete anything else.
318+ """
319+ for folder , files in tree .items ():
320+ folder_path = base_folder / folder
321+ folder_path .mkdir (parents = True , exist_ok = True )
322+ for file , contents in files .items ():
323+ file_path = folder_path / file
324+ file_path .write_text (contents )
278325
279326
280327def main ():
@@ -303,7 +350,7 @@ def main():
303350 exceptions_to_generate : dict [Type , bool ] = {}
304351 functions_to_patch = []
305352
306- for interface in wit .package ("fastly:compute@0.0.0-prerelease.0" ).interfaces ():
353+ for interface in wit .package (PACKAGE ).interfaces ():
307354 for function in interface .functions ():
308355 if error_type := function .error_type_of_returned_result ():
309356 if not isinstance (error_type , NullType ):
@@ -319,9 +366,9 @@ def main():
319366 # identifiable:
320367 functions_to_patch .append ((interface , function ))
321368
322- print ( generate_exception_classes (exceptions_to_generate .keys ()))
323- generate_function_patches ( functions_to_patch )
324-
369+ write_files ( exception_code_tree (exceptions_to_generate .keys ()),
370+ Path ( __file__ ). parent . parent / "fastly_compute" / "exceptions" )
371+ #generate_function_patches(functions_to_patch)
325372
326373
327374if __name__ == "__main__" :
0 commit comments