1+ # frozen_string_literal: true
2+
3+ module Generator
4+ class Python < Base
5+ def format_enum_value ( value )
6+ value . downcase . gsub ( /[.\/ +\s -]/ , '_' )
7+ end
8+
9+ def get_sorted_properties ( definition )
10+ required_fields = definition [ 'required' ] || [ ]
11+ definition [ 'properties' ] . sort_by do |name , *|
12+ [ required_fields . include? ( name ) ? 0 : 1 , name ]
13+ end
14+ end
15+
16+ def format_property ( parent_type_name , property_name , property , required_fields )
17+ snake_name = property_name . gsub ( /([A-Z]+)([A-Z][a-z])/ , '\1_\2' )
18+ . gsub ( /([a-z\d ])([A-Z])/ , '\1_\2' )
19+ . downcase
20+
21+ property_type = get_property_type ( parent_type_name , property_name , property )
22+ is_required = required_fields . include? ( property_name )
23+
24+ property_description = if property [ 'description' ] && !property [ 'description' ] . include? ( "\n " )
25+ " # #{ property [ 'description' ] } "
26+ else
27+ ''
28+ end
29+ if is_required
30+ "#{ snake_name } : #{ property_type } #{ property_description } "
31+ else
32+ "#{ snake_name } : Optional[#{ property_type } ] = None#{ property_description } "
33+ end
34+ end
35+
36+ def get_property_type ( parent_type_name , property_name , property )
37+ type = type_for ( parent_type_name , property_name , property )
38+ type . match? ( /\A [A-Z]/ ) ? class_name ( type ) : type
39+ end
40+
41+ def array_type_for ( type_name )
42+ inner_type = if language_translations_for_data_types . values . include? ( type_name )
43+ type_name # Keep primitive types as is
44+ else
45+ class_name ( type_name ) # CamelCase for complex types
46+ end
47+ inner_type
48+ end
49+
50+ def format_description ( raw_description , indent_string : ' ' )
51+ return '""" """' if raw_description . nil?
52+
53+ lines = raw_description . split ( "\n " ) . map { |line |
54+ if line . strip . empty?
55+ ""
56+ else
57+ "#{ indent_string } #{ line . rstrip } "
58+ end
59+ }
60+
61+ %("""\n #{ lines . join ( "\n " ) } \n #{ indent_string } """)
62+ end
63+
64+ def language_translations_for_data_types
65+ {
66+ 'integer' => 'int' ,
67+ 'string' => 'str' ,
68+ 'boolean' => 'bool' ,
69+ 'array' => 'list'
70+ }
71+ end
72+
73+ private
74+
75+ def default_value ( parent_type_name , property_name , property )
76+ if property [ 'type' ] == 'string'
77+ default_value_for_string ( parent_type_name , property_name , property )
78+ elsif property [ 'type' ] == 'integer'
79+ '0'
80+ elsif property [ 'type' ] == 'boolean'
81+ 'False'
82+ elsif property [ 'type' ] == 'array'
83+ '[]'
84+ elsif property [ '$ref' ]
85+ "#{ class_name ( type_for ( parent_type_name , nil , property ) ) } ()"
86+ else
87+ 'None'
88+ end
89+ end
90+
91+ def default_value_for_string ( parent_type_name , property_name , property )
92+ if property [ 'enum' ]
93+ enum_type_name = type_for ( parent_type_name , property_name , property )
94+ "#{ class_name ( enum_type_name ) } .#{ enum_constant ( property [ 'enum' ] [ 0 ] ) } "
95+ else
96+ '""'
97+ end
98+ end
99+
100+ def type_for ( parent_type_name , property_name , property )
101+ if property [ '$ref' ]
102+ property_type_from_ref ( property [ '$ref' ] )
103+ elsif property [ 'type' ]
104+ property_type_from_type ( parent_type_name , property_name , property , type : property [ 'type' ] )
105+ else
106+ raise "Property #{ property_name } did not define 'type' or '$ref'"
107+ end
108+ end
109+
110+ def property_type_from_type ( parent_type_name , property_name , property , type :)
111+ if type == 'array'
112+ type = type_for ( parent_type_name , nil , property [ 'items' ] )
113+ inner_type = array_type_for ( type )
114+ "list[#{ inner_type } ]"
115+ elsif property [ 'enum' ]
116+ enum_name ( parent_type_name , property_name , property [ 'enum' ] )
117+ else
118+ language_translations_for_data_types . fetch ( type )
119+ end
120+ end
121+
122+ def enum_constant ( value )
123+ value . gsub ( /[.\/ +]/ , '_' ) . downcase
124+ end
125+
126+ def enum_name ( parent_type_name , property_name , enum )
127+ "#{ class_name ( parent_type_name ) } #{ capitalize ( property_name ) } " . tap do |name |
128+ @enum_set . add ( { name : name , values : enum } )
129+ end
130+ end
131+
132+ def property_type_from_ref ( ref )
133+ class_name ( ref )
134+ end
135+
136+ def class_name ( ref )
137+ return ref if language_translations_for_data_types . values . include? ( ref )
138+
139+ # Remove .json extension if present
140+ name = ref . sub ( /\. json$/ , '' )
141+ # Get the basename without path
142+ name = File . basename ( name )
143+ # Convert each word to proper case, handling camelCase and snake_case
144+ parts = name . gsub ( /[._-]/ , '_' ) . split ( '_' ) . map do |part |
145+ # Split by any existing camelCase
146+ subparts = part . scan ( /[A-Z][a-z]*|[a-z]+/ )
147+ subparts . map ( &:capitalize ) . join
148+ end
149+ # Join all parts to create final CamelCase name
150+ parts . join
151+ end
152+ end
153+ end
0 commit comments