9
9
import json
10
10
import logging
11
11
import os
12
- import tomllib
13
12
from enum import Enum
14
13
from pathlib import Path
15
14
from typing import Annotated , Callable
29
28
logger = logging .getLogger ("tenint.connector" )
30
29
31
30
32
- class ConfigurationError (Exception ):
33
- pass
34
-
35
-
36
31
class LogLevel (str , Enum ):
37
32
notset = "notset"
38
33
debug = "debug"
@@ -105,53 +100,63 @@ def log_env_vars(self) -> None:
105
100
value = "{{ HIDDEN }}"
106
101
logger .debug (f'EnvVar { key } ="{ value } "' )
107
102
108
- def fetch_config (
109
- self ,
110
- data : str | None = None ,
111
- fn : Path | None = None ,
112
- ) -> BaseModel :
103
+ def fetch_config (self , data : str ) -> (BaseModel , int ):
113
104
"""
114
105
Fetch and validate the configuration from either the data string or the filepath
115
106
and return the settings object to the caller.
116
107
117
108
Args:
118
- data:
119
- The string object of the
109
+ data: JSON formatted string of the settings
110
+
111
+ Returns:
112
+ The pydantic settings model and the status code.
113
+ """
114
+ try :
115
+ return self .settings (** json .loads (data )), 0
116
+ except ValidationError as e :
117
+ self .console .print (f"JSON String validation failed: { e } " )
118
+ except Exception as _ :
119
+ self .console .print_exception ()
120
+ return None , 2
121
+
122
+ def callback (
123
+ self , url : str | None , resp : dict , job_id : str | None , status_code : int
124
+ ) -> None :
120
125
"""
121
- settings = None
122
-
123
- # If a string object is passed, we will handle that first.
124
- if data :
125
- settings = self .settings (** json .loads (data ))
126
-
127
- # If a file has been passed in instead an the suffix appears to be a JSON
128
- # suffix, then we will assume a JSON file and handle accordingly.
129
- elif fn and fn .is_file () and fn .suffix .lower () in [".json" , ".jsn" ]:
130
- with fn .open ("r" , encoding = "utf-8" ) as fobj :
131
- settings = self .settings (** json .load (fobj ))
132
-
133
- # If the file passed has a TOML suffix, we will process as a toml file through
134
- # tomllib.
135
- elif fn and fn .is_file () and fn .suffix .lower () in [".toml" , ".tml" ]:
136
- with fn .open ("rb" ) as fobj :
137
- settings = self .settings (** tomllib .load (fobj ))
138
-
139
- # If we processed anything, then return the settings object, otherwise raise a
140
- # ConfigurationError
141
- if settings :
142
- logger .debug (f"Job config={ settings .model_dump (mode = 'json' )} " )
143
- return settings
144
- raise ConfigurationError ("No valid configurations passed." )
126
+ Initiate the callback response to the job scheduler
127
+
128
+ Args:
129
+ url: Callback url
130
+ resp: Dictionary response of the job
131
+ job_id: The Job UUID
132
+ status_code: Job status
133
+ """
134
+ # Set the Callback payload to the job response if the response is a dictionary
135
+ try :
136
+ payload = CallbackResponse (exit_code = status_code , ** resp ).model_dump (
137
+ mode = "json"
138
+ )
139
+ except (ValidationError , TypeError ) as _ :
140
+ logger .error (f"Job response format is invalid: { resp = } " )
141
+ payload = CallbackResponse (exit_code = status_code ).model_dump (mode = "json" )
142
+
143
+ # If a callback and a job id have been set, then we will initiate a callback
144
+ # to the job manager with the response payload of the job to inform the manager
145
+ # that we have finished.
146
+ if job_id and url :
147
+ requests .post (url , headers = {"X-Job-ID" : job_id }, json = payload , verify = False )
148
+ logger .info (f"Called back to { url = } with { job_id = } and { payload = } " )
149
+ else :
150
+ logger .warning ("Did not initiate a callback!" )
151
+ logger .info (f"callback={ payload } " )
145
152
146
153
def cmd_config (
147
154
self , pretty : Annotated [bool , Option (help = "Pretty format the response" )] = False
148
155
):
149
156
"""
150
157
Return the connector configuration
151
158
"""
152
- indent = 2
153
- if not pretty :
154
- indent = None
159
+ indent = 2 if pretty else None
155
160
156
161
class Config (Configuration ):
157
162
settings_model : type [Settings ] = self .settings
@@ -173,23 +178,14 @@ def cmd_validate(self):
173
178
def cmd_run (
174
179
self ,
175
180
json_data : Annotated [
176
- str | None ,
181
+ str ,
177
182
Option (
178
183
"--json" ,
179
184
"-j" ,
180
185
envvar = "CONFIG_JSON" ,
181
186
help = "The JSON config object as a string" ,
182
187
),
183
- ] = None ,
184
- filename : Annotated [
185
- Path | None ,
186
- Option (
187
- "--filename" ,
188
- "-f" ,
189
- envvar = "CONFIG_FILENAME" ,
190
- help = "Filename of either a json or toml file containing the job config" ,
191
- ),
192
- ] = None ,
188
+ ],
193
189
job_id : Annotated [
194
190
str | None ,
195
191
Option (
@@ -209,15 +205,15 @@ def cmd_run(
209
205
),
210
206
] = None ,
211
207
log_level : Annotated [
212
- LogLevel ,
208
+ LogLevel | None ,
213
209
Option (
214
210
"--log-level" ,
215
211
"-l" ,
216
212
envvar = "LOG_LEVEL" ,
217
213
help = "Sets the logging verbosity for the job" ,
218
214
case_sensitive = False ,
219
215
),
220
- ] = LogLevel . info ,
216
+ ] = None ,
221
217
since : Annotated [
222
218
int | None ,
223
219
Option (
@@ -231,13 +227,24 @@ def cmd_run(
231
227
"""
232
228
Invoke the connector
233
229
"""
230
+ resp = None
231
+ config , status_code = self .fetch_config (json_data )
232
+
233
+ # Set the log level, using local before config and then ultimately setting the
234
+ # log level to debug if nothing has been set.
235
+ if log_level :
236
+ lvl = log_level .upper ()
237
+ elif config :
238
+ lvl = config .log_level
239
+ else :
240
+ lvl = "DEBUG"
241
+
242
+ # Configure the logging handlers
234
243
logging .basicConfig (
235
- level = log_level .upper (),
236
- # format="%(asctime) %(name)s(%(filename)s:%(lineno)d): %(message)s",
244
+ level = lvl ,
237
245
format = "%(name)s: %(message)s" ,
238
246
datefmt = "%Y-%m-%d %H:%M:%S" ,
239
247
handlers = [
240
- # logging.FileHandler("job.log"),
241
248
RichHandler (
242
249
console = self .console ,
243
250
show_path = False ,
@@ -248,46 +255,26 @@ def cmd_run(
248
255
logging .StreamHandler (),
249
256
],
250
257
)
258
+
259
+ # Log the environment variables and configuration data
251
260
self .log_env_vars ()
252
261
logger .info (f"Logging to { self .logfile } " )
253
- status_code = 0
254
- resp = None
262
+ logger . debug ( f"Job { json_data = } " )
263
+ logger . info ( f"Job config= { config . model_dump_json () if config else None } " )
255
264
256
265
# Attempt to run the connector job and handle any errors that may be thrown
257
266
# in a graceful way.
258
267
try :
259
- config = self .fetch_config (json_data , filename )
260
- resp = self .main (config = config , since = since )
261
- except (ValidationError , ConfigurationError ) as e :
262
- logging .error (f"Invalid configuration presented: { e } " )
263
- status_code = 2
268
+ if status_code == 0 :
269
+ resp = self .main (config = config , since = since )
264
270
except Exception as _ :
265
271
logging .exception ("Job run failed with error" , stack_info = 2 )
266
272
status_code = 1
267
273
268
- # Set the Callback payload to the job response if the response is a dictionary
269
- try :
270
- payload = CallbackResponse (exit_code = status_code , ** resp ).model_dump (
271
- mode = "json"
272
- )
273
- except (ValidationError , TypeError ) as _ :
274
- logger .error (f"Job response format is invalid: { resp = } " )
275
- payload = CallbackResponse (exit_code = status_code ).model_dump (mode = "json" )
276
-
277
- # If a callback and a job id have been set, then we will initiate a callback
278
- # to the job manager with the response payload of the job to inform the manager
279
- # that we have finished.
280
- if job_id and callback_url :
281
- requests .post (
282
- callback_url ,
283
- headers = {"X-Job-ID" : job_id },
284
- json = payload ,
285
- verify = False ,
286
- )
287
- logger .info (f"Called back to { callback_url = } with { job_id = } and { payload = } " )
288
- else :
289
- logger .warning ("Did not initiate a callback!" )
290
- logger .info (f"callback={ payload } " )
274
+ # Initiate the callback to the job management system.
275
+ self .callback (
276
+ url = callback_url , job_id = job_id , resp = resp , status_code = status_code
277
+ )
291
278
292
279
# Exit the connector with the status code from the runner.
293
280
raise Exit (code = status_code )
0 commit comments