32
32
lstrip_blocks = True ,
33
33
)
34
34
query_py = jinja_env .get_template ("query.py.tpl" )
35
+ aggs_py = jinja_env .get_template ("aggs.py.tpl" )
35
36
types_py = jinja_env .get_template ("types.py.tpl" )
36
37
37
38
# map with name replacements for Elasticsearch attributes
43
44
"_types.query_dsl:DistanceFeatureQuery" : "_types.query_dsl:DistanceFeatureQueryBase" ,
44
45
}
45
46
47
+ # some aggregation types are complicated to determine from the schema, so they
48
+ # have their correct type here
49
+ AGG_TYPES = {
50
+ "bucket_count_ks_test" : "Pipeline" ,
51
+ "bucket_correlation" : "Pipeline" ,
52
+ "bucket_sort" : "Bucket" ,
53
+ "categorize_text" : "Bucket" ,
54
+ "filter" : "Bucket" ,
55
+ "moving_avg" : "Pipeline" ,
56
+ "variable_width_histogram" : "Bucket" ,
57
+ }
58
+
59
+
60
+ def property_to_class_name (name ):
61
+ return "" .join ([w .title () if w != "ip" else "IP" for w in name .split ("_" )])
62
+
46
63
47
64
def wrapped_doc (text , width = 70 , initial_indent = "" , subsequent_indent = "" ):
48
65
"""Formats a docstring as a list of lines of up to the request width."""
@@ -101,6 +118,18 @@ def find_type(self, name, namespace=None):
101
118
):
102
119
return t
103
120
121
+ def inherits_from (self , type_ , name , namespace = None ):
122
+ while "inherits" in type_ :
123
+ type_ = self .find_type (
124
+ type_ ["inherits" ]["type" ]["name" ],
125
+ type_ ["inherits" ]["type" ]["namespace" ],
126
+ )
127
+ if type_ ["name" ]["name" ] == name and (
128
+ namespace is None or type_ ["name" ]["namespace" ] == namespace
129
+ ):
130
+ return True
131
+ return False
132
+
104
133
def get_python_type (self , schema_type ):
105
134
"""Obtain Python typing details for a given schema type
106
135
@@ -156,7 +185,9 @@ def get_python_type(self, schema_type):
156
185
# for dicts we use Mapping[key_type, value_type]
157
186
key_type , key_param = self .get_python_type (schema_type ["key" ])
158
187
value_type , value_param = self .get_python_type (schema_type ["value" ])
159
- return f"Mapping[{ key_type } , { value_type } ]" , None
188
+ return f"Mapping[{ key_type } , { value_type } ]" , (
189
+ {** value_param , "hash" : True } if value_param else None
190
+ )
160
191
161
192
elif schema_type ["kind" ] == "union_of" :
162
193
if (
@@ -334,17 +365,38 @@ def property_to_python_class(self, p):
334
365
"""
335
366
k = {
336
367
"property_name" : p ["name" ],
337
- "name" : "" . join ([ w . title () for w in p ["name" ]. split ( "_" ) ]),
368
+ "name" : property_to_class_name ( p ["name" ]),
338
369
}
339
370
k ["docstring" ] = wrapped_doc (p .get ("description" ) or "" )
371
+ other_classes = []
340
372
kind = p ["type" ]["kind" ]
341
373
if kind == "instance_of" :
342
374
namespace = p ["type" ]["type" ]["namespace" ]
343
375
name = p ["type" ]["type" ]["name" ]
344
376
if f"{ namespace } :{ name } " in TYPE_REPLACEMENTS :
345
377
namespace , name = TYPE_REPLACEMENTS [f"{ namespace } :{ name } " ].split (":" )
346
- type_ = schema .find_type (name , namespace )
378
+ if name == "QueryContainer" and namespace == "_types.query_dsl" :
379
+ type_ = {
380
+ "kind" : "interface" ,
381
+ "properties" : [p ],
382
+ }
383
+ else :
384
+ type_ = schema .find_type (name , namespace )
385
+ if p ["name" ] in AGG_TYPES :
386
+ k ["parent" ] = AGG_TYPES [p ["name" ]]
387
+
347
388
if type_ ["kind" ] == "interface" :
389
+ # set the correct parent for bucket and pipeline aggregations
390
+ if self .inherits_from (
391
+ type_ , "PipelineAggregationBase" , "_types.aggregations"
392
+ ):
393
+ k ["parent" ] = "Pipeline"
394
+ elif self .inherits_from (
395
+ type_ , "BucketAggregationBase" , "_types.aggregations"
396
+ ):
397
+ k ["parent" ] = "Bucket"
398
+
399
+ # generate class attributes
348
400
k ["args" ] = []
349
401
k ["params" ] = []
350
402
if "behaviors" in type_ :
@@ -397,6 +449,21 @@ def property_to_python_class(self, p):
397
449
)
398
450
else :
399
451
break
452
+
453
+ elif type_ ["kind" ] == "type_alias" :
454
+ if type_ ["type" ]["kind" ] == "union_of" :
455
+ # for unions we create sub-classes
456
+ for other in type_ ["type" ]["items" ]:
457
+ other_class = self .interface_to_python_class (
458
+ other ["type" ]["name" ], self .interfaces , for_types_py = False
459
+ )
460
+ other_class ["parent" ] = k ["name" ]
461
+ other_classes .append (other_class )
462
+ else :
463
+ raise RuntimeError (
464
+ "Cannot generate code for instances of type_alias instances that are not unions."
465
+ )
466
+
400
467
else :
401
468
raise RuntimeError (
402
469
f"Cannot generate code for instances of kind '{ type_ ['kind' ]} '"
@@ -444,9 +511,9 @@ def property_to_python_class(self, p):
444
511
445
512
else :
446
513
raise RuntimeError (f"Cannot generate code for type { p ['type' ]} " )
447
- return k
514
+ return [ k ] + other_classes
448
515
449
- def interface_to_python_class (self , interface , interfaces ):
516
+ def interface_to_python_class (self , interface , interfaces , for_types_py = True ):
450
517
"""Return a dictionary with template data necessary to render an
451
518
interface a Python class.
452
519
@@ -477,7 +544,7 @@ def interface_to_python_class(self, interface, interfaces):
477
544
k = {"name" : interface , "args" : []}
478
545
while True :
479
546
for arg in type_ ["properties" ]:
480
- schema .add_attribute (k , arg , for_types_py = True )
547
+ schema .add_attribute (k , arg , for_types_py = for_types_py )
481
548
482
549
if "inherits" not in type_ or "type" not in type_ ["inherits" ]:
483
550
break
@@ -500,13 +567,28 @@ def generate_query_py(schema, filename):
500
567
classes = []
501
568
query_container = schema .find_type ("QueryContainer" , "_types.query_dsl" )
502
569
for p in query_container ["properties" ]:
503
- classes . append ( schema .property_to_python_class (p ) )
570
+ classes += schema .property_to_python_class (p )
504
571
505
572
with open (filename , "wt" ) as f :
506
573
f .write (query_py .render (classes = classes , parent = "Query" ))
507
574
print (f"Generated { filename } ." )
508
575
509
576
577
+ def generate_aggs_py (schema , filename ):
578
+ """Generate aggs.py with all the properties of `AggregationContainer` as
579
+ Python classes.
580
+ """
581
+ classes = []
582
+ aggs_container = schema .find_type ("AggregationContainer" , "_types.aggregations" )
583
+ for p in aggs_container ["properties" ]:
584
+ if "containerProperty" not in p or not p ["containerProperty" ]:
585
+ classes += schema .property_to_python_class (p )
586
+
587
+ with open (filename , "wt" ) as f :
588
+ f .write (aggs_py .render (classes = classes , parent = "Agg" ))
589
+ print (f"Generated { filename } ." )
590
+
591
+
510
592
def generate_types_py (schema , filename ):
511
593
"""Generate types.py"""
512
594
classes = {}
@@ -542,4 +624,5 @@ def generate_types_py(schema, filename):
542
624
if __name__ == "__main__" :
543
625
schema = ElasticsearchSchema ()
544
626
generate_query_py (schema , "elasticsearch_dsl/query.py" )
627
+ generate_aggs_py (schema , "elasticsearch_dsl/aggs.py" )
545
628
generate_types_py (schema , "elasticsearch_dsl/types.py" )
0 commit comments