@@ -64,83 +64,123 @@ def load_report_def(self, plugin: str, filename: str):
64
64
)[1 ]
65
65
return filename , report_def
66
66
67
-
68
67
async def render (self , * args , ** kwargs ) -> ReportEnv :
69
- if 'input' in self .report_def :
70
- self .env .params = await parse_input (self , kwargs , self .report_def ['input' ])
68
+ # Cache the `report_def` locally for faster lookups and readability
69
+ report_def = self .report_def
70
+ env = self .env
71
+
72
+ # Parse input parameters or copy kwargs
73
+ if 'input' in report_def :
74
+ env .params = await parse_input (self , kwargs , report_def ['input' ])
71
75
else :
72
- self .env .params = kwargs .copy ()
73
- # add the bot to be able to access the whole environment from inside the report
74
- self .env .params ['bot' ] = self .bot
75
- # format the embed
76
- if 'color' in self .report_def :
77
- self .env .embed = discord .Embed (color = getattr (discord .Color , self .report_def .get ('color' , 'blue' ))())
76
+ env .params = kwargs .copy ()
77
+
78
+ # Add bot reference to params
79
+ env .params ['bot' ] = self .bot
80
+
81
+ # Create an embed with optional color
82
+ embed_color = getattr (discord .Color , report_def .get ('color' , 'blue' ), discord .Color .blue )()
83
+ env .embed = discord .Embed (color = embed_color )
84
+
85
+ # Predefine keys that need formatting and apply transformations
86
+ formatted_keys = {
87
+ 'title' : {'max_length' : 256 , 'setter' : lambda val : setattr (env .embed , 'title' , val )},
88
+ 'description' : {'max_length' : 4096 , 'setter' : lambda val : setattr (env .embed , 'description' , val )},
89
+ 'url' : {'setter' : lambda val : setattr (env .embed , 'url' , val )},
90
+ 'img' : {'setter' : lambda val : env .embed .set_thumbnail (url = val )},
91
+ 'footer' : {
92
+ 'setter' : lambda val : env .embed .set_footer (
93
+ text = f"{ env .embed .footer .text or '' } \n { val } " [:2048 ]
94
+ )
95
+ },
96
+ }
97
+
98
+ for key , config in formatted_keys .items ():
99
+ if value := report_def .get (key ):
100
+ formatted_value = utils .format_string (value , ** env .params )
101
+ if 'max_length' in config :
102
+ formatted_value = formatted_value [:config ['max_length' ]]
103
+ config ['setter' ](formatted_value )
104
+
105
+ # Process mentions
106
+ if mention := report_def .get ('mention' ):
107
+ if isinstance (mention , int ):
108
+ env .mention = f"<@&{ mention } >"
109
+ else :
110
+ env .mention = '' .join ([f"<@&{ x } >" for x in mention ])
111
+
112
+ # Handle the 'elements' section
113
+ if elements := report_def .get ('elements' ):
114
+ for element in elements :
115
+ await self ._process_element (element , env .params )
116
+
117
+ return env
118
+
119
+ async def _process_element (self , element , params ):
120
+ """
121
+ Helper function to process individual elements sequentially.
122
+ """
123
+ # Resolve the element's class and arguments
124
+ element_class , element_args = self ._resolve_element_class_and_args (element , params )
125
+
126
+ if not element_class :
127
+ return # Skip if the class couldn't be resolved
128
+
129
+ # Filter arguments for the __init__ method
130
+ init_args = self ._filter_args (element_args , element_class .__init__ )
131
+ instance = element_class (self .env , ** init_args )
132
+
133
+ if not isinstance (instance , ReportElement ):
134
+ raise UnknownReportElement (element .get ('class' , str (element )))
135
+
136
+ # Filter arguments for the render method
137
+ render_args = self ._filter_args (element_args , instance .render )
138
+
139
+ # Render the element and handle exceptions
140
+ try :
141
+ await instance .render (** render_args )
142
+ except (TimeoutError , asyncio .TimeoutError ):
143
+ self .log .error (f"Timeout while processing report { self .filename } ! Some elements might be empty." )
144
+ except psycopg .OperationalError :
145
+ self .log .error (f"Database error while processing report { self .filename } ! Some elements might be empty." )
146
+ except Exception :
147
+ self .log .error (f"Error while processing report { self .filename } ! Some elements might be empty." ,
148
+ exc_info = True )
149
+ raise
150
+
151
+ def _resolve_element_class_and_args (self , element , params ):
152
+ """
153
+ Resolves the class and arguments for a given element.
154
+ """
155
+ if isinstance (element , dict ):
156
+ element_args = parse_params (params , element .get ('params' , params .copy ()))
157
+ class_name = element .get ('class' ) or element .get ('type' )
158
+ element_class = None
159
+
160
+ # Dynamically retrieve the class instance
161
+ if class_name :
162
+ element_class = (
163
+ utils .str_to_class (class_name )
164
+ if 'class' in element
165
+ else getattr (sys .modules ['core.report.elements' ], class_name , None )
166
+ )
167
+ elif isinstance (element , str ):
168
+ element_class = getattr (sys .modules ['core.report.elements' ], element , None )
169
+ element_args = params .copy ()
78
170
else :
79
- self .env .embed = discord .Embed ()
80
- for name , item in self .report_def .items ():
81
- # parse report parameters
82
- if name == 'title' :
83
- self .env .embed .title = utils .format_string (item , ** self .env .params )[:256 ]
84
- elif name == 'mention' :
85
- if isinstance (item , int ):
86
- self .env .mention = f'<@&{ item } >'
87
- else :
88
- self .env .mention = '' .join ([f"<@&{ x } >" for x in item ])
89
- elif name == 'description' :
90
- self .env .embed .description = utils .format_string (item , ** self .env .params )[:4096 ]
91
- elif name == 'url' :
92
- self .env .embed .url = utils .format_string (item , ** self .env .params )
93
- elif name == 'img' :
94
- self .env .embed .set_thumbnail (url = utils .format_string (item , ** self .env .params ))
95
- elif name == 'footer' :
96
- footer = self .env .embed .footer .text or ''
97
- text = utils .format_string (item , ** self .env .params )
98
- if footer is None :
99
- footer = text
100
- else :
101
- footer += '\n ' + text
102
- self .env .embed .set_footer (text = footer [:2048 ])
103
- elif name == 'elements' :
104
- for element in item :
105
- if isinstance (element , dict ):
106
- if 'params' in element :
107
- element_args = parse_params (self .env .params , element ['params' ])
108
- else :
109
- element_args = self .env .params .copy ()
110
- element_class = utils .str_to_class (element ['class' ]) if 'class' in element else None
111
- if not element_class and 'type' in element :
112
- element_class = getattr (sys .modules ['core.report.elements' ], element ['type' ])
113
- elif isinstance (element , str ):
114
- element_class = getattr (sys .modules ['core.report.elements' ], element )
115
- element_args = self .env .params .copy ()
116
- else :
117
- raise UnknownReportElement (str (element ))
118
- if element_class :
119
- # remove parameters, that are not in the class __init__ signature
120
- signature = inspect .signature (element_class .__init__ ).parameters .keys ()
121
- class_args = {name : value for name , value in element_args .items () if name in signature }
122
- element_class = element_class (self .env , ** class_args )
123
- if isinstance (element_class , ReportElement ):
124
- # remove parameters, that are not in the render classes signature
125
- signature = inspect .signature (element_class .render ).parameters .keys ()
126
- render_args = {name : value for name , value in element_args .items () if name in signature }
127
- try :
128
- await element_class .render (** render_args )
129
- except (TimeoutError , asyncio .TimeoutError ):
130
- self .log .error (f"Timeout while processing report { self .filename } ! "
131
- f"Some elements might be empty." )
132
- except psycopg .OperationalError :
133
- self .log .error (f"Database error while processing report { self .filename } ! "
134
- f"Some elements might be empty." )
135
- except Exception :
136
- self .log .error (f"Error while processing report { self .filename } ! "
137
- f"Some elements might be empty." , exc_info = True )
138
- raise
139
- else :
140
- raise UnknownReportElement (element ['class' ])
141
- else :
142
- raise ClassNotFound (element ['class' ])
143
- return self .env
171
+ raise UnknownReportElement (str (element ))
172
+
173
+ if not element_class :
174
+ raise ClassNotFound (str (element .get ('class' , element )))
175
+
176
+ return element_class , element_args
177
+
178
+ def _filter_args (self , args , method ):
179
+ """
180
+ Filters arguments based on a method's signature, ensuring compatibility.
181
+ """
182
+ signature = inspect .signature (method ).parameters
183
+ return {name : value for name , value in args .items () if name in signature }
144
184
145
185
146
186
class Pagination (ABC ):
0 commit comments