@@ -74,31 +74,38 @@ class ReleaseType(str, Enum):
7474 Attributes:
7575 NORMAL: Standard workflow release execution
7676 RERUN: Re-execution of previously failed workflow
77- EVENT: Event-triggered workflow execution
77+ DRYRUN: Dry-execution workflow
7878 FORCE: Forced execution bypassing normal conditions
7979 """
8080
8181 NORMAL = "normal"
8282 RERUN = "rerun"
83- EVENT = "event"
8483 FORCE = "force"
84+ DRYRUN = "dryrun"
8585
8686
8787NORMAL = ReleaseType .NORMAL
8888RERUN = ReleaseType .RERUN
89- EVENT = ReleaseType .EVENT
89+ DRYRUN = ReleaseType .DRYRUN
9090FORCE = ReleaseType .FORCE
9191
9292
9393class AuditData (BaseModel ):
94- """Audit Data model."""
94+ """Audit Data model that use to be the core data for any Audit model manage
95+ logging at the target pointer system or service like file-system, sqlite
96+ database, etc.
97+ """
9598
9699 model_config = ConfigDict (use_enum_values = True )
97100
98101 name : str = Field (description = "A workflow name." )
99102 release : datetime = Field (description = "A release datetime." )
100103 type : ReleaseType = Field (
101- default = NORMAL , description = "A running type before logging."
104+ default = NORMAL ,
105+ description = (
106+ "An execution type that should be value in ('normal', 'rerun', "
107+ "'force', 'dryrun')."
108+ ),
102109 )
103110 context : DictData = Field (
104111 default_factory = dict ,
@@ -121,18 +128,17 @@ class BaseAudit(BaseModel, ABC):
121128 for logging subclasses like file, sqlite, etc.
122129 """
123130
124- type : str
131+ type : Literal ["base" ] = "base"
132+ logging_name : str = "ddeutil.workflow"
125133 extras : DictData = Field (
126134 default_factory = dict ,
127135 description = "An extras parameter that want to override core config" ,
128136 )
129137
130138 @field_validator ("extras" , mode = "before" )
131- def validate_extras (cls , v : Any ) -> DictData :
139+ def __prepare_extras (cls , v : Any ) -> Any :
132140 """Validate extras field to ensure it's a dictionary."""
133- if v is None :
134- return {}
135- return v
141+ return {} if v is None else v
136142
137143 @model_validator (mode = "after" )
138144 def __model_action (self ) -> Self :
@@ -148,7 +154,7 @@ def __model_action(self) -> Self:
148154 self .do_before ()
149155
150156 # NOTE: Start setting log config in this line with cache.
151- set_logging ("ddeutil.workflow" )
157+ set_logging (self . logging_name )
152158 return self
153159
154160 @abstractmethod
@@ -248,27 +254,33 @@ def save(
248254 raise NotImplementedError ("Audit should implement `save` method." )
249255
250256
251- class FileAudit (BaseAudit ):
257+ class LocalFileAudit (BaseAudit ):
252258 """File Audit Pydantic Model for saving log data from workflow execution.
253259
254260 This class inherits from BaseAudit and implements file-based storage
255261 for audit logs. It saves workflow execution results to JSON files
256262 in a structured directory hierarchy.
257263
258264 Attributes:
259- filename_fmt: Class variable defining the filename format for audit files.
265+ file_fmt: Class variable defining the filename format for audit log.
266+ file_release_fmt: Class variable defining the filename format for audit
267+ release log.
260268 """
261269
262- filename_fmt : ClassVar [str ] = (
263- "workflow={name}/release={release:%Y%m%d%H%M%S}"
264- )
270+ file_fmt : ClassVar [str ] = "workflow={name}"
271+ file_release_fmt : ClassVar [str ] = "release={release:%Y%m%d%H%M%S}"
265272
266273 type : Literal ["file" ] = "file"
267- path : str = Field (
268- default = "./audits" ,
274+ path : Path = Field (
275+ default = Path ( "./audits" ) ,
269276 description = "A file path that use to manage audit logs." ,
270277 )
271278
279+ @field_validator ("path" , mode = "before" , json_schema_input_type = str )
280+ def __prepare_path (cls , data : Any ) -> Any :
281+ """Prepare path that passing with string to Path instance."""
282+ return Path (data ) if isinstance (data , str ) else data
283+
272284 def do_before (self ) -> None :
273285 """Create directory of release before saving log file.
274286
@@ -278,7 +290,10 @@ def do_before(self) -> None:
278290 Path (self .path ).mkdir (parents = True , exist_ok = True )
279291
280292 def find_audits (
281- self , name : str , * , extras : Optional [DictData ] = None
293+ self ,
294+ name : str ,
295+ * ,
296+ extras : Optional [DictData ] = None ,
282297 ) -> Iterator [AuditData ]:
283298 """Generate audit data found from logs path for a specific workflow name.
284299
@@ -292,7 +307,7 @@ def find_audits(
292307 Raises:
293308 FileNotFoundError: If the workflow directory does not exist.
294309 """
295- pointer : Path = Path ( self .path ) / f"workflow= { name } "
310+ pointer : Path = self .path / self . file_fmt . format ( name = name )
296311 if not pointer .exists ():
297312 raise FileNotFoundError (f"Pointer: { pointer .absolute ()} ." )
298313
@@ -325,7 +340,7 @@ def find_audit_with_release(
325340 ValueError: If no releases found when release is None.
326341 """
327342 if release is None :
328- pointer : Path = Path ( self .path ) / f"workflow= { name } "
343+ pointer : Path = self .path / self . file_fmt . format ( name = name )
329344 if not pointer .exists ():
330345 raise FileNotFoundError (f"Pointer: { pointer .absolute ()} ." )
331346
@@ -382,8 +397,10 @@ def pointer(self, data: AuditData) -> Path:
382397 Returns:
383398 Path: The directory path for the current workflow and release.
384399 """
385- return Path (self .path ) / self .filename_fmt .format (
386- name = data .name , release = data .release
400+ return (
401+ self .path
402+ / self .file_fmt .format (** data .model_dump (by_alias = True ))
403+ / self .file_release_fmt .format (** data .model_dump (by_alias = True ))
387404 )
388405
389406 def save (self , data : Any , excluded : Optional [list [str ]] = None ) -> Self :
@@ -459,19 +476,19 @@ def cleanup(self, max_age_days: int = 180) -> int: # pragma: no cov
459476 return cleaned_count
460477
461478
462- class SQLiteAudit (BaseAudit ): # pragma: no cov
479+ class LocalSQLiteAudit (BaseAudit ): # pragma: no cov
463480 """SQLite Audit model for database-based audit storage.
464481
465482 This class inherits from BaseAudit and implements SQLite database storage
466483 for audit logs with compression support.
467484
468485 Attributes:
469486 table_name: Class variable defining the database table name.
470- schemas : Class variable defining the database schema.
487+ ddl : Class variable defining the database schema.
471488 """
472489
473490 table_name : ClassVar [str ] = "audits"
474- schemas : ClassVar [
491+ ddl : ClassVar [
475492 str
476493 ] = """
477494 CREATE TABLE IF NOT EXISTS audits (
@@ -489,22 +506,21 @@ class SQLiteAudit(BaseAudit): # pragma: no cov
489506 """
490507
491508 type : Literal ["sqlite" ] = "sqlite"
492- path : str
509+ path : Path = Field (
510+ default = Path ("./audits.db" ),
511+ description = "A SQLite filepath." ,
512+ )
493513
494- def _ensure_table_exists (self ) -> None :
514+ def do_before (self ) -> None :
495515 """Ensure the audit table exists in the database."""
496- audit_url = dynamic ("audit_url" , extras = self .extras )
497- if audit_url is None or not audit_url .path :
516+ if self .path .is_dir ():
498517 raise ValueError (
499- "SQLite audit_url must specify a database file path"
518+ "SQLite path must specify a database file path not dir. "
500519 )
501520
502- audit_url_parse : ParseResult = urlparse (audit_url )
503- db_path = Path (audit_url_parse .path )
504- db_path .parent .mkdir (parents = True , exist_ok = True )
505-
506- with sqlite3 .connect (db_path ) as conn :
507- conn .execute (self .schemas )
521+ self .path .parent .mkdir (parents = True , exist_ok = True )
522+ with sqlite3 .connect (self .path ) as conn :
523+ conn .execute (self .ddl )
508524 conn .commit ()
509525
510526 def is_pointed (
@@ -771,24 +787,31 @@ def cleanup(self, max_age_days: int = 180) -> int:
771787 return cursor .rowcount
772788
773789
790+ class PostgresAudit (BaseAudit , ABC ): ... # pragma: no cov
791+
792+
774793Audit = Annotated [
775794 Union [
776- FileAudit ,
777- SQLiteAudit ,
795+ LocalFileAudit ,
796+ LocalSQLiteAudit ,
778797 ],
779798 Field (discriminator = "type" ),
780799]
781800
782801
783- def get_audit (extras : Optional [DictData ] = None ) -> Audit : # pragma: no cov
802+ def get_audit (
803+ audit_conf : Optional [DictData ] = None ,
804+ extras : Optional [DictData ] = None ,
805+ ) -> Audit : # pragma: no cov
784806 """Get an audit model dynamically based on the config audit path value.
785807
786808 Args:
809+ audit_conf (DictData):
787810 extras: Optional extra parameters to override the core config.
788811
789812 Returns:
790813 Audit: The appropriate audit model class based on configuration.
791814 """
792- audit_conf = dynamic ("audit_conf" , extras = extras )
815+ audit_conf = dynamic ("audit_conf" , f = audit_conf , extras = extras )
793816 model = TypeAdapter (Audit ).validate_python (audit_conf | {"extras" : extras })
794817 return model
0 commit comments