foo-py 0.1.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- agents.py +10690 -0
- app.py +16234 -0
- boogr/__init__.py +563 -0
- boogr/default_icon.ico +0 -0
- boogr/enums.py +381 -0
- boogr/minion.py +175 -0
- boogr/resources/ico/BooIcon.ico +0 -0
- boogr/resources/ico/Booger.ico +0 -0
- boogr/resources/ico/Save.ico +0 -0
- boogr/resources/ico/adobe.ico +0 -0
- boogr/resources/ico/atk.ico +0 -0
- boogr/resources/ico/b.ico +0 -0
- boogr/resources/ico/batch.ico +0 -0
- boogr/resources/ico/black_sigma.ico +0 -0
- boogr/resources/ico/boo.ico +0 -0
- boogr/resources/ico/boogr.ico +0 -0
- boogr/resources/ico/browse.ico +0 -0
- boogr/resources/ico/chart.ico +0 -0
- boogr/resources/ico/copy.ico +0 -0
- boogr/resources/ico/csv.ico +0 -0
- boogr/resources/ico/dataedit.ico +0 -0
- boogr/resources/ico/doc.ico +0 -0
- boogr/resources/ico/e_logo.ico +0 -0
- boogr/resources/ico/error.ico +0 -0
- boogr/resources/ico/euler_circle.ico +0 -0
- boogr/resources/ico/excel.ico +0 -0
- boogr/resources/ico/file_browse.ico +0 -0
- boogr/resources/ico/filter.ico +0 -0
- boogr/resources/ico/folder_browse.ico +0 -0
- boogr/resources/ico/info.ico +0 -0
- boogr/resources/ico/input.ico +0 -0
- boogr/resources/ico/koolaid.ico +0 -0
- boogr/resources/ico/machinelearning.ico +0 -0
- boogr/resources/ico/message.ico +0 -0
- boogr/resources/ico/pdf.ico +0 -0
- boogr/resources/ico/pi.ico +0 -0
- boogr/resources/ico/setting.ico +0 -0
- boogr/resources/ico/sword_ninja.ico +0 -0
- boogr/resources/ico/textfile.ico +0 -0
- boogr/resources/ico/webcam.ico +0 -0
- boogr/resources/img/atk.png +0 -0
- boogr/resources/img/boogr.png +0 -0
- boogr/resources/img/button/Authority.png +0 -0
- boogr/resources/img/button/BOC.png +0 -0
- boogr/resources/img/button/DERA.png +0 -0
- boogr/resources/img/button/DWH.png +0 -0
- boogr/resources/img/button/EMD.png +0 -0
- boogr/resources/img/button/OAR.png +0 -0
- boogr/resources/img/button/OECA.png +0 -0
- boogr/resources/img/button/OGC.png +0 -0
- boogr/resources/img/button/OMS.png +0 -0
- boogr/resources/img/button/ORD.png +0 -0
- boogr/resources/img/button/OW.png +0 -0
- boogr/resources/img/button/RCRA.png +0 -0
- boogr/resources/img/button/TSCA.png +0 -0
- boogr/resources/img/button/WIFIA.png +0 -0
- boogr/resources/img/button/access.png +0 -0
- boogr/resources/img/button/add.png +0 -0
- boogr/resources/img/button/adobe.png +0 -0
- boogr/resources/img/button/airline.png +0 -0
- boogr/resources/img/button/analytics.png +0 -0
- boogr/resources/img/button/appropriation.png +0 -0
- boogr/resources/img/button/atk.ico +0 -0
- boogr/resources/img/button/atk.png +0 -0
- boogr/resources/img/button/attachment.png +0 -0
- boogr/resources/img/button/bfy.png +0 -0
- boogr/resources/img/button/bluetooth.png +0 -0
- boogr/resources/img/button/browse.png +0 -0
- boogr/resources/img/button/budget.png +0 -0
- boogr/resources/img/button/calculator.png +0 -0
- boogr/resources/img/button/calendar.png +0 -0
- boogr/resources/img/button/cancel.png +0 -0
- boogr/resources/img/button/categoricalgrants.png +0 -0
- boogr/resources/img/button/chart.png +0 -0
- boogr/resources/img/button/chrome.png +0 -0
- boogr/resources/img/button/close.png +0 -0
- boogr/resources/img/button/columndelete.png +0 -0
- boogr/resources/img/button/columnedit.png +0 -0
- boogr/resources/img/button/columninsert.png +0 -0
- boogr/resources/img/button/commandline.png +0 -0
- boogr/resources/img/button/commute.png +0 -0
- boogr/resources/img/button/compass.png +0 -0
- boogr/resources/img/button/contracts.png +0 -0
- boogr/resources/img/button/controlpanel.png +0 -0
- boogr/resources/img/button/csv.png +0 -0
- boogr/resources/img/button/database.png +0 -0
- boogr/resources/img/button/databaseadd.png +0 -0
- boogr/resources/img/button/databasedelete.png +0 -0
- boogr/resources/img/button/databaserefresh.png +0 -0
- boogr/resources/img/button/databasesql.png +0 -0
- boogr/resources/img/button/databaseverify.png +0 -0
- boogr/resources/img/button/datagrid.png +0 -0
- boogr/resources/img/button/delete.png +0 -0
- boogr/resources/img/button/division.png +0 -0
- boogr/resources/img/button/document.png +0 -0
- boogr/resources/img/button/documentadd.png +0 -0
- boogr/resources/img/button/documentation.png +0 -0
- boogr/resources/img/button/documentdelete.png +0 -0
- boogr/resources/img/button/documentedit.png +0 -0
- boogr/resources/img/button/documenterror.png +0 -0
- boogr/resources/img/button/documentsearch.png +0 -0
- boogr/resources/img/button/edge.png +0 -0
- boogr/resources/img/button/edit.png +0 -0
- boogr/resources/img/button/efy.png +0 -0
- boogr/resources/img/button/environment.png +0 -0
- boogr/resources/img/button/ev.png +0 -0
- boogr/resources/img/button/excel.png +0 -0
- boogr/resources/img/button/expenses.png +0 -0
- boogr/resources/img/button/export.png +0 -0
- boogr/resources/img/button/file.png +0 -0
- boogr/resources/img/button/file_word.png +0 -0
- boogr/resources/img/button/fileadd.png +0 -0
- boogr/resources/img/button/filebrowse.png +0 -0
- boogr/resources/img/button/filecopy.png +0 -0
- boogr/resources/img/button/filedelete.png +0 -0
- boogr/resources/img/button/fileedit.png +0 -0
- boogr/resources/img/button/filereader.png +0 -0
- boogr/resources/img/button/filesearch.png +0 -0
- boogr/resources/img/button/filetransfer.png +0 -0
- boogr/resources/img/button/fileverify.png +0 -0
- boogr/resources/img/button/filewriter.png +0 -0
- boogr/resources/img/button/filter.png +0 -0
- boogr/resources/img/button/first.png +0 -0
- boogr/resources/img/button/folder.png +0 -0
- boogr/resources/img/button/folderbrowse.png +0 -0
- boogr/resources/img/button/foldercompress.png +0 -0
- boogr/resources/img/button/foldercopy.png +0 -0
- boogr/resources/img/button/folderdownload.png +0 -0
- boogr/resources/img/button/folderopen.png +0 -0
- boogr/resources/img/button/fte.png +0 -0
- boogr/resources/img/button/function.png +0 -0
- boogr/resources/img/button/gmail.png +0 -0
- boogr/resources/img/button/go.png +0 -0
- boogr/resources/img/button/google.png +0 -0
- boogr/resources/img/button/grants.png +0 -0
- boogr/resources/img/button/guidance.png +0 -0
- boogr/resources/img/button/home.png +0 -0
- boogr/resources/img/button/id.png +0 -0
- boogr/resources/img/button/image.png +0 -0
- boogr/resources/img/button/import.png +0 -0
- boogr/resources/img/button/information.png +0 -0
- boogr/resources/img/button/internet.png +0 -0
- boogr/resources/img/button/justice.png +0 -0
- boogr/resources/img/button/last.png +0 -0
- boogr/resources/img/button/ledger.png +0 -0
- boogr/resources/img/button/left.png +0 -0
- boogr/resources/img/button/levels.png +0 -0
- boogr/resources/img/button/logout.png +0 -0
- boogr/resources/img/button/lust.png +0 -0
- boogr/resources/img/button/menu.png +0 -0
- boogr/resources/img/button/metrics.png +0 -0
- boogr/resources/img/button/mpg.png +0 -0
- boogr/resources/img/button/next.png +0 -0
- boogr/resources/img/button/no.png +0 -0
- boogr/resources/img/button/oil.png +0 -0
- boogr/resources/img/button/ok.png +0 -0
- boogr/resources/img/button/omb.png +0 -0
- boogr/resources/img/button/onenote.png +0 -0
- boogr/resources/img/button/oust.png +0 -0
- boogr/resources/img/button/outlay.png +0 -0
- boogr/resources/img/button/outlook.png +0 -0
- boogr/resources/img/button/pause.png +0 -0
- boogr/resources/img/button/payroll.png +0 -0
- boogr/resources/img/button/pdf.png +0 -0
- boogr/resources/img/button/percentage.png +0 -0
- boogr/resources/img/button/play.png +0 -0
- boogr/resources/img/button/plusminus.png +0 -0
- boogr/resources/img/button/previous.png +0 -0
- boogr/resources/img/button/print.png +0 -0
- boogr/resources/img/button/recertification.png +0 -0
- boogr/resources/img/button/recycle.png +0 -0
- boogr/resources/img/button/redo.png +0 -0
- boogr/resources/img/button/refresh.png +0 -0
- boogr/resources/img/button/remove.png +0 -0
- boogr/resources/img/button/reserve.png +0 -0
- boogr/resources/img/button/right.png +0 -0
- boogr/resources/img/button/row.png +0 -0
- boogr/resources/img/button/rowcopy.png +0 -0
- boogr/resources/img/button/rowdelete.png +0 -0
- boogr/resources/img/button/rowedit.png +0 -0
- boogr/resources/img/button/rowinsert.png +0 -0
- boogr/resources/img/button/save.png +0 -0
- boogr/resources/img/button/scan.png +0 -0
- boogr/resources/img/button/sharepoint.png +0 -0
- boogr/resources/img/button/sigma.png +0 -0
- boogr/resources/img/button/site.png +0 -0
- boogr/resources/img/button/sitetravel.png +0 -0
- boogr/resources/img/button/sort.png +0 -0
- boogr/resources/img/button/spreadsheet.png +0 -0
- boogr/resources/img/button/statistics.png +0 -0
- boogr/resources/img/button/table.png +0 -0
- boogr/resources/img/button/tableadd.png +0 -0
- boogr/resources/img/button/tabledelete.png +0 -0
- boogr/resources/img/button/tablesettings.png +0 -0
- boogr/resources/img/button/text.png +0 -0
- boogr/resources/img/button/traffic.png +0 -0
- boogr/resources/img/button/travel.png +0 -0
- boogr/resources/img/button/undelete.png +0 -0
- boogr/resources/img/button/undo.png +0 -0
- boogr/resources/img/button/wcf.png +0 -0
- boogr/resources/img/button/windows.png +0 -0
- boogr/resources/img/button/word.png +0 -0
- boogr/resources/img/button/xml.png +0 -0
- boogr/resources/img/button/yes.png +0 -0
- boogr/resources/img/button/zipfile.png +0 -0
- boogr/resources/img/gooey.png +0 -0
- boogr/resources/img/web/google.png +0 -0
- config.py +1015 -0
- core.py +170 -0
- data.py +832 -0
- desktop.py +173 -0
- embedders.py +269 -0
- fetchers.py +25142 -0
- foo_assets/__init__.py +1 -0
- foo_assets/resources/images/favicon.ico +0 -0
- foo_assets/resources/images/foo-apikeys.png +0 -0
- foo_assets/resources/images/foo-architecture.png +0 -0
- foo_assets/resources/images/foo-workflows.png +0 -0
- foo_assets/resources/images/foo.ico +0 -0
- foo_assets/resources/images/foo.svg +60 -0
- foo_assets/resources/images/foo_logo.ico +0 -0
- foo_assets/resources/images/foo_logo.png +0 -0
- foo_assets/resources/images/foo_logo.svg +63 -0
- foo_assets/resources/images/foo_portfolio.png +0 -0
- foo_assets/resources/images/foo_project.png +0 -0
- foo_assets/resources/images/system_diagram.svg +46 -0
- foo_assets/resources/images/uml_class_diagram.svg +65 -0
- foo_assets/streamlit_config.toml +85 -0
- foo_cli.py +52 -0
- foo_py-0.1.1.dist-info/METADATA +596 -0
- foo_py-0.1.1.dist-info/RECORD +242 -0
- foo_py-0.1.1.dist-info/WHEEL +5 -0
- foo_py-0.1.1.dist-info/entry_points.txt +2 -0
- foo_py-0.1.1.dist-info/licenses/LICENSE.txt +21 -0
- foo_py-0.1.1.dist-info/top_level.txt +18 -0
- generators.py +3323 -0
- loaders.py +4570 -0
- models.py +344 -0
- processors.py +3216 -0
- scrapers.py +706 -0
- stores/vector.py +365 -0
- writers.py +268 -0
boogr/__init__.py
ADDED
|
@@ -0,0 +1,563 @@
|
|
|
1
|
+
'''
|
|
2
|
+
******************************************************************************************
|
|
3
|
+
Assembly: boogr
|
|
4
|
+
Filename: init.py
|
|
5
|
+
Author: Terry D. Eppler
|
|
6
|
+
Created: 05-31-2022
|
|
7
|
+
|
|
8
|
+
Last Modified By: Terry D. Eppler
|
|
9
|
+
Last Modified On: 05-01-2025
|
|
10
|
+
******************************************************************************************
|
|
11
|
+
<copyright file="init.py" company="Terry D. Eppler">
|
|
12
|
+
|
|
13
|
+
init.py is part of a data analysis tool integrating GenAI, Text Processing,
|
|
14
|
+
and Machine-Learning algorithms for federal analysts.
|
|
15
|
+
Copyright © 2022 Terry Eppler
|
|
16
|
+
|
|
17
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
18
|
+
of this software and associated documentation files (the “Software”),
|
|
19
|
+
to deal in the Software without restriction,
|
|
20
|
+
including without limitation the rights to use,
|
|
21
|
+
copy, modify, merge, publish, distribute, sublicense,
|
|
22
|
+
and/or sell copies of the Software,
|
|
23
|
+
and to permit persons to whom the Software is furnished to do so,
|
|
24
|
+
subject to the following conditions:
|
|
25
|
+
|
|
26
|
+
The above copyright notice and this permission notice shall be included in all
|
|
27
|
+
copies or substantial portions of the Software.
|
|
28
|
+
|
|
29
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
|
30
|
+
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
31
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
|
|
32
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
|
33
|
+
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
|
34
|
+
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
35
|
+
DEALINGS IN THE SOFTWARE.
|
|
36
|
+
|
|
37
|
+
You can contact me at: terryeppler@gmail.com or eppler.terry@epa.gov
|
|
38
|
+
|
|
39
|
+
</copyright>
|
|
40
|
+
<summary>
|
|
41
|
+
init.py
|
|
42
|
+
</summary>
|
|
43
|
+
******************************************************************************************
|
|
44
|
+
'''
|
|
45
|
+
from __future__ import annotations
|
|
46
|
+
from pathlib import Path
|
|
47
|
+
import traceback
|
|
48
|
+
from sys import exc_info
|
|
49
|
+
from typing import List, Optional
|
|
50
|
+
import re
|
|
51
|
+
import sqlite3
|
|
52
|
+
import config as cfg
|
|
53
|
+
import datetime
|
|
54
|
+
from datetime import timedelta
|
|
55
|
+
|
|
56
|
+
def get_config_bool( name: str, default: bool = False ) -> bool:
|
|
57
|
+
"""Read a Boolean value from the configuration module.
|
|
58
|
+
|
|
59
|
+
Purpose:
|
|
60
|
+
Safely read a Boolean configuration value without requiring every deployment of Fiddy to
|
|
61
|
+
define the newest configuration switches. Missing values return the supplied default.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
name (str): Configuration attribute name.
|
|
65
|
+
default (bool): Default value used when the attribute is not available.
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
bool: Configuration Boolean value or the supplied default.
|
|
69
|
+
"""
|
|
70
|
+
try:
|
|
71
|
+
value = getattr( cfg, name, default )
|
|
72
|
+
return bool( value )
|
|
73
|
+
except Exception:
|
|
74
|
+
return default
|
|
75
|
+
|
|
76
|
+
def get_config_int( name: str, default: int ) -> int:
|
|
77
|
+
"""Read an integer value from the configuration module.
|
|
78
|
+
|
|
79
|
+
Purpose:
|
|
80
|
+
Safely read an integer configuration value without requiring every deployment of Fiddy to
|
|
81
|
+
define the newest configuration switches. Missing or invalid values return the supplied
|
|
82
|
+
default.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
name (str): Configuration attribute name.
|
|
86
|
+
default (int): Default value used when the attribute is not available or invalid.
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
int: Configuration integer value or the supplied default.
|
|
90
|
+
"""
|
|
91
|
+
try:
|
|
92
|
+
value = getattr( cfg, name, default )
|
|
93
|
+
return int( value )
|
|
94
|
+
except Exception:
|
|
95
|
+
return default
|
|
96
|
+
|
|
97
|
+
def sanitize_text( value: object, max_length: int = 1000 ) -> str:
|
|
98
|
+
"""Return a reviewer-safe and log-safe text representation.
|
|
99
|
+
|
|
100
|
+
Purpose:
|
|
101
|
+
Remove or mask sensitive values before any exception metadata is persisted. This helper
|
|
102
|
+
masks email addresses, Windows paths, POSIX paths, temporary-file paths, common uploaded
|
|
103
|
+
file references, long quoted payloads, long comma-delimited manifest-like rows, and long
|
|
104
|
+
free-form OCR-like text. Raw text logging can only be enabled explicitly through
|
|
105
|
+
``cfg.ENABLE_RAW_TEXT_LOGGING``.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
value (object): Source value to sanitize.
|
|
109
|
+
max_length (int): Maximum returned string length after masking.
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
str: Sanitized text value suitable for local diagnostic logging.
|
|
113
|
+
"""
|
|
114
|
+
try:
|
|
115
|
+
if value is None:
|
|
116
|
+
return ''
|
|
117
|
+
|
|
118
|
+
text = str( value )
|
|
119
|
+
text = text.replace( '\x00', '' )
|
|
120
|
+
text = re.sub( r'[\r\n\t]+', ' ', text )
|
|
121
|
+
text = re.sub( r'\s{2,}', ' ', text ).strip( )
|
|
122
|
+
|
|
123
|
+
if not get_config_bool( 'ENABLE_FILE_PATH_LOGGING', False ):
|
|
124
|
+
text = re.sub( r'[A-Za-z]:\\(?:[^\\/:*?"<>|\r\n]+\\)*[^\\/:*?"<>|\r\n]*', '[PATH]',
|
|
125
|
+
text )
|
|
126
|
+
text = re.sub( r'/(?:tmp|mnt|var|home|Users|user|workspace|app|data)(?:/[^\s,;:)]*)+',
|
|
127
|
+
'[PATH]', text )
|
|
128
|
+
text = re.sub( r'\\(?:tmp|temp|users|appdata|programdata)\\[^\s,;:)]*', '[PATH]', text,
|
|
129
|
+
flags=re.IGNORECASE )
|
|
130
|
+
|
|
131
|
+
text = re.sub( r'\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b', '[EMAIL]', text,
|
|
132
|
+
flags=re.IGNORECASE )
|
|
133
|
+
text = re.sub( r'\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b',
|
|
134
|
+
'[GUID]', text, flags=re.IGNORECASE )
|
|
135
|
+
text = re.sub(
|
|
136
|
+
r'(?i)(password|secret|token|api[_-]?key|subscription[_-]?key)\s*[=:]\s*[^\s,;]+',
|
|
137
|
+
r'\1=[SECRET]', text )
|
|
138
|
+
|
|
139
|
+
if not get_config_bool( 'ENABLE_RAW_TEXT_LOGGING', False ):
|
|
140
|
+
text = re.sub( r'"[^"]{120,}"', '"[LONG_TEXT]"', text )
|
|
141
|
+
text = re.sub( r"'[^']{120,}'", "'[LONG_TEXT]'", text )
|
|
142
|
+
|
|
143
|
+
if len( text ) > 300 and text.count( ',' ) >= 5:
|
|
144
|
+
text = '[MANIFEST_ROW]'
|
|
145
|
+
elif len( text ) > 500 and len( re.findall( r'\b[A-Za-z]{3,}\b', text ) ) > 60:
|
|
146
|
+
text = '[LONG_TEXT]'
|
|
147
|
+
|
|
148
|
+
return text[ :max_length ]
|
|
149
|
+
except Exception:
|
|
150
|
+
return '[SANITIZATION_FAILED]'
|
|
151
|
+
|
|
152
|
+
def sanitize_traceback( value: object, max_length: int = 4000 ) -> str:
|
|
153
|
+
"""Return a sanitized traceback string.
|
|
154
|
+
|
|
155
|
+
Purpose:
|
|
156
|
+
Normalize and mask traceback text before it is stored in SQLite. This function delegates to
|
|
157
|
+
``sanitize_text`` and uses a larger maximum length appropriate for diagnostic stack traces.
|
|
158
|
+
|
|
159
|
+
Args:
|
|
160
|
+
value (object): Traceback value to sanitize.
|
|
161
|
+
max_length (int): Maximum returned string length.
|
|
162
|
+
|
|
163
|
+
Returns:
|
|
164
|
+
str: Sanitized traceback text.
|
|
165
|
+
"""
|
|
166
|
+
try:
|
|
167
|
+
return sanitize_text( value, max_length=max_length )
|
|
168
|
+
except Exception:
|
|
169
|
+
return '[TRACE_SANITIZATION_FAILED]'
|
|
170
|
+
|
|
171
|
+
class Error( Exception ):
|
|
172
|
+
"""Wrap a Python exception with structured and sanitized diagnostic metadata.
|
|
173
|
+
|
|
174
|
+
Purpose:
|
|
175
|
+
The ``Error`` class extends ``Exception`` and stores the original exception together with
|
|
176
|
+
context fields used by Fiddy logging and diagnostics. The wrapper captures a sanitized
|
|
177
|
+
message, exception type, sanitized traceback, component or class cause, module name, method
|
|
178
|
+
or function signature, optional heading, and combined information string.
|
|
179
|
+
|
|
180
|
+
The object remains intentionally lightweight. Callers generally create an ``Error`` inside an
|
|
181
|
+
``except`` block, assign stable metadata fields such as ``cause``, ``module``, and ``method``,
|
|
182
|
+
and then pass the object to ``Logger.write`` for local persistence.
|
|
183
|
+
|
|
184
|
+
Attributes:
|
|
185
|
+
error (Optional[Exception]): Source exception being wrapped.
|
|
186
|
+
heading (Optional[str]): Optional sanitized user-facing heading or category.
|
|
187
|
+
cause (Optional[str]): Sanitized component, class, or module purpose associated with the failure.
|
|
188
|
+
method (Optional[str]): Sanitized method or function signature associated with the failure.
|
|
189
|
+
module (Optional[str]): Sanitized module name associated with the failure.
|
|
190
|
+
type (Optional[type]): Exception type captured from ``sys.exc_info``.
|
|
191
|
+
trace (Optional[str]): Sanitized formatted traceback captured at wrapper creation time.
|
|
192
|
+
info (Optional[str]): Combined sanitized exception type and traceback information.
|
|
193
|
+
message (Optional[str]): Sanitized string representation of the source exception.
|
|
194
|
+
"""
|
|
195
|
+
|
|
196
|
+
error: Optional[ Exception ]
|
|
197
|
+
heading: Optional[ str ]
|
|
198
|
+
cause: Optional[ str ]
|
|
199
|
+
method: Optional[ str ]
|
|
200
|
+
module: Optional[ str ]
|
|
201
|
+
type: Optional[ type ]
|
|
202
|
+
trace: Optional[ str ]
|
|
203
|
+
info: Optional[ str ]
|
|
204
|
+
message: Optional[ str ]
|
|
205
|
+
|
|
206
|
+
def __init__( self, error: Exception, heading: str = None, cause: str = None,
|
|
207
|
+
method: str = None, module: str = None ) -> None:
|
|
208
|
+
"""Initialize an error wrapper from a caught exception.
|
|
209
|
+
|
|
210
|
+
Purpose:
|
|
211
|
+
Store the original exception and optional context values, initialize the base
|
|
212
|
+
``Exception`` with the sanitized exception message, capture the current exception type,
|
|
213
|
+
capture a sanitized formatted traceback, and build a combined sanitized information
|
|
214
|
+
string suitable for database logging.
|
|
215
|
+
|
|
216
|
+
Args:
|
|
217
|
+
error (Exception): Source exception being wrapped.
|
|
218
|
+
heading (str): Optional user-facing heading.
|
|
219
|
+
cause (str): Optional component, class, or module purpose that caused the error.
|
|
220
|
+
method (str): Optional stable method or function signature where the error occurred.
|
|
221
|
+
module (str): Optional module where the error occurred.
|
|
222
|
+
|
|
223
|
+
Returns:
|
|
224
|
+
None.
|
|
225
|
+
"""
|
|
226
|
+
self.error = error
|
|
227
|
+
self.heading = sanitize_text( heading, 120 ) if heading else None
|
|
228
|
+
self.cause = sanitize_text( cause, 120 ) if cause else None
|
|
229
|
+
self.method = sanitize_text( method, 180 ) if method else None
|
|
230
|
+
self.module = sanitize_text( module, 120 ) if module else None
|
|
231
|
+
self.type = exc_info( )[ 0 ]
|
|
232
|
+
self.message = sanitize_text( str( error ) if error else '',
|
|
233
|
+
get_config_int( 'MAX_LOG_MESSAGE_CHARS', 1000 ) )
|
|
234
|
+
self.trace = sanitize_traceback( traceback.format_exc( ),
|
|
235
|
+
get_config_int( 'MAX_LOG_TRACE_CHARS', 4000 ) )
|
|
236
|
+
self.info = sanitize_text( f'{str( self.type )}: {self.trace}',
|
|
237
|
+
get_config_int( 'MAX_LOG_TRACE_CHARS', 4000 ) )
|
|
238
|
+
super( ).__init__( self.message )
|
|
239
|
+
|
|
240
|
+
def __setattr__( self, name: str, value: object ) -> None:
|
|
241
|
+
"""Sanitize public metadata fields when they are assigned after construction.
|
|
242
|
+
|
|
243
|
+
Purpose:
|
|
244
|
+
Preserve the established Fiddy usage pattern where callers create ``Error(e)`` and then
|
|
245
|
+
assign ``cause``, ``module``, and ``method`` afterward. Metadata assignments are
|
|
246
|
+
sanitized automatically so delayed assignment does not bypass privacy controls.
|
|
247
|
+
|
|
248
|
+
Args:
|
|
249
|
+
name (str): Attribute name being assigned.
|
|
250
|
+
value (object): Attribute value being assigned.
|
|
251
|
+
|
|
252
|
+
Returns:
|
|
253
|
+
None.
|
|
254
|
+
"""
|
|
255
|
+
if name in ('heading', 'cause', 'module') and value is not None:
|
|
256
|
+
object.__setattr__( self, name, sanitize_text( value, 120 ) )
|
|
257
|
+
elif name == 'method' and value is not None:
|
|
258
|
+
object.__setattr__( self, name, sanitize_text( value, 180 ) )
|
|
259
|
+
elif name == 'message' and value is not None:
|
|
260
|
+
object.__setattr__( self, name,
|
|
261
|
+
sanitize_text( value, get_config_int( 'MAX_LOG_MESSAGE_CHARS', 1000 ) ) )
|
|
262
|
+
elif name in ('trace', 'info') and value is not None:
|
|
263
|
+
object.__setattr__( self, name,
|
|
264
|
+
sanitize_traceback( value, get_config_int( 'MAX_LOG_TRACE_CHARS', 4000 ) ) )
|
|
265
|
+
else:
|
|
266
|
+
object.__setattr__( self, name, value )
|
|
267
|
+
|
|
268
|
+
def __str__( self ) -> str:
|
|
269
|
+
"""Return a string representation of the wrapped error.
|
|
270
|
+
|
|
271
|
+
Purpose:
|
|
272
|
+
Return the sanitized information string when available, otherwise return the sanitized
|
|
273
|
+
source exception message. If neither value is available, return an empty string.
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
Returns:
|
|
278
|
+
str: Sanitized error information string.
|
|
279
|
+
"""
|
|
280
|
+
return self.info or self.message or ''
|
|
281
|
+
|
|
282
|
+
def __dir__( self ) -> List[ str ]:
|
|
283
|
+
"""Return public member names used by callers and display surfaces.
|
|
284
|
+
|
|
285
|
+
Purpose:
|
|
286
|
+
Expose the primary fields that are useful for logging, diagnostics, and reviewer-safe
|
|
287
|
+
display. Implementation details and inherited exception internals are intentionally
|
|
288
|
+
omitted from the returned member list.
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
Returns:
|
|
293
|
+
List[str]: Public error member names.
|
|
294
|
+
"""
|
|
295
|
+
return [
|
|
296
|
+
'message',
|
|
297
|
+
'cause',
|
|
298
|
+
'error',
|
|
299
|
+
'method',
|
|
300
|
+
'module',
|
|
301
|
+
'trace',
|
|
302
|
+
'info'
|
|
303
|
+
]
|
|
304
|
+
|
|
305
|
+
class Logger( ):
|
|
306
|
+
"""Persist sanitized ``Error`` objects to the configured local SQLite database.
|
|
307
|
+
|
|
308
|
+
The ``Logger`` class resolves the configured log database path and table name, creates the
|
|
309
|
+
database table when needed, truncates sanitized values to fit configured limits, writes one
|
|
310
|
+
row per error, and optionally purges old records. The logger is intentionally defensive:
|
|
311
|
+
logger failures return conservative fallback values rather than raising secondary exceptions
|
|
312
|
+
into application workflows.
|
|
313
|
+
|
|
314
|
+
Attributes:
|
|
315
|
+
path (Path): Resolved SQLite database path.
|
|
316
|
+
table_name (str): Safe SQLite table name used for inserts and maintenance.
|
|
317
|
+
"""
|
|
318
|
+
path: Path
|
|
319
|
+
table_name: str
|
|
320
|
+
|
|
321
|
+
def __init__( self ) -> None:
|
|
322
|
+
"""Initialize the logger from configured database and table settings.
|
|
323
|
+
|
|
324
|
+
Purpose:
|
|
325
|
+
Resolve the SQLite database path through ``get_database_path`` and the table name
|
|
326
|
+
through ``get_table_name``. No database connection is opened until ``ensure_database``,
|
|
327
|
+
``write``, or ``purge_old_logs`` is called.
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
Returns:
|
|
332
|
+
None.
|
|
333
|
+
"""
|
|
334
|
+
self.path = self.get_database_path( )
|
|
335
|
+
self.table_name = self.get_table_name( )
|
|
336
|
+
|
|
337
|
+
def get_database_path( self ) -> Path:
|
|
338
|
+
"""Return the configured SQLite database path.
|
|
339
|
+
|
|
340
|
+
Purpose:
|
|
341
|
+
Read ``cfg.LOG_PATH`` and resolve it to an absolute ``Path``. Relative log paths are
|
|
342
|
+
resolved under ``cfg.ROOT_DIR`` when available. If path resolution fails, the fallback
|
|
343
|
+
path ``logging/Exceptions.db`` is resolved relative to the current working directory.
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
Returns:
|
|
348
|
+
Path: Resolved SQLite database path.
|
|
349
|
+
"""
|
|
350
|
+
try:
|
|
351
|
+
value = getattr( cfg, 'LOG_PATH', 'logging/Exceptions.db' )
|
|
352
|
+
path = Path( value )
|
|
353
|
+
|
|
354
|
+
if not path.is_absolute( ):
|
|
355
|
+
path = getattr( cfg, 'ROOT_DIR', Path.cwd( ) ) / path
|
|
356
|
+
|
|
357
|
+
return path.resolve( )
|
|
358
|
+
except Exception:
|
|
359
|
+
return Path( 'logging/Exceptions.db' ).resolve( )
|
|
360
|
+
|
|
361
|
+
def get_table_name( self ) -> str:
|
|
362
|
+
"""Return a safe SQLite table name.
|
|
363
|
+
|
|
364
|
+
Purpose:
|
|
365
|
+
Read ``cfg.LOG_FILE`` and accept it only when it matches a conservative SQLite
|
|
366
|
+
identifier pattern beginning with a letter or underscore and containing only letters,
|
|
367
|
+
digits, and underscores. Unsafe or unavailable values fall back to ``Exceptions``.
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
Returns:
|
|
372
|
+
str: Safe SQLite table name.
|
|
373
|
+
"""
|
|
374
|
+
try:
|
|
375
|
+
value = str( getattr( cfg, 'LOG_FILE', 'Exceptions' ) ).strip( )
|
|
376
|
+
|
|
377
|
+
if re.fullmatch( r'[A-Za-z_][A-Za-z0-9_]*', value ):
|
|
378
|
+
return value
|
|
379
|
+
|
|
380
|
+
return 'Exceptions'
|
|
381
|
+
except Exception:
|
|
382
|
+
return 'Exceptions'
|
|
383
|
+
|
|
384
|
+
def truncate( self, value: object, length: int ) -> str:
|
|
385
|
+
"""Sanitize, convert, and truncate a value to a maximum length.
|
|
386
|
+
|
|
387
|
+
Purpose:
|
|
388
|
+
Standardize values before database insertion. ``None`` values become empty strings, all
|
|
389
|
+
other values are sanitized and converted to text, and the text is sliced to the requested
|
|
390
|
+
length so it fits the logging schema.
|
|
391
|
+
|
|
392
|
+
Args:
|
|
393
|
+
value (object): Source value to sanitize, convert, and truncate.
|
|
394
|
+
length (int): Maximum string length.
|
|
395
|
+
|
|
396
|
+
Returns:
|
|
397
|
+
str: Sanitized and truncated string, or an empty string when conversion fails.
|
|
398
|
+
"""
|
|
399
|
+
try:
|
|
400
|
+
if value is None:
|
|
401
|
+
return ''
|
|
402
|
+
|
|
403
|
+
text = sanitize_text( value, max_length=length )
|
|
404
|
+
return text[ :length ]
|
|
405
|
+
except Exception:
|
|
406
|
+
return ''
|
|
407
|
+
|
|
408
|
+
def ensure_database( self ) -> None:
|
|
409
|
+
"""Create the log directory and configured error table when needed.
|
|
410
|
+
|
|
411
|
+
Purpose:
|
|
412
|
+
Create the parent directory for the configured SQLite database path and create the
|
|
413
|
+
configured table if it does not already exist. The table stores timestamp, cause, module,
|
|
414
|
+
method, message, info, and trace values, with an autoincrementing primary key.
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
Returns:
|
|
419
|
+
None.
|
|
420
|
+
"""
|
|
421
|
+
try:
|
|
422
|
+
if not get_config_bool( 'ENABLE_EXCEPTION_LOGGING', True ):
|
|
423
|
+
return None
|
|
424
|
+
|
|
425
|
+
self.path.parent.mkdir( parents=True, exist_ok=True )
|
|
426
|
+
sql = f'''
|
|
427
|
+
CREATE TABLE IF NOT EXISTS "{self.table_name}" (
|
|
428
|
+
"ID" INTEGER NOT NULL UNIQUE,
|
|
429
|
+
"created_on" TEXT NOT NULL,
|
|
430
|
+
"cause" TEXT,
|
|
431
|
+
"module" TEXT,
|
|
432
|
+
"method" TEXT,
|
|
433
|
+
"message" TEXT,
|
|
434
|
+
"info" TEXT,
|
|
435
|
+
"trace" TEXT,
|
|
436
|
+
PRIMARY KEY("ID" AUTOINCREMENT)
|
|
437
|
+
)
|
|
438
|
+
'''
|
|
439
|
+
|
|
440
|
+
with sqlite3.connect( self.path ) as connection:
|
|
441
|
+
connection.execute( sql )
|
|
442
|
+
connection.commit( )
|
|
443
|
+
except Exception:
|
|
444
|
+
return None
|
|
445
|
+
|
|
446
|
+
def write( self, error: Error ) -> int:
|
|
447
|
+
"""Write one sanitized ``Error`` object to the configured SQLite error table.
|
|
448
|
+
|
|
449
|
+
Purpose:
|
|
450
|
+
Ensure the database exists, build a parameterized insert statement, sanitize and truncate
|
|
451
|
+
error fields, write the row, commit the transaction, optionally purge expired log rows,
|
|
452
|
+
and return the inserted row identifier. If exception logging is disabled or logging fails,
|
|
453
|
+
return ``0`` without raising another exception.
|
|
454
|
+
|
|
455
|
+
Args:
|
|
456
|
+
error (Error): Error object to persist.
|
|
457
|
+
|
|
458
|
+
Returns:
|
|
459
|
+
int: Inserted row identifier, or ``0`` when the error is missing, disabled, or logging fails.
|
|
460
|
+
"""
|
|
461
|
+
try:
|
|
462
|
+
if not error or not get_config_bool( 'ENABLE_EXCEPTION_LOGGING', True ):
|
|
463
|
+
return 0
|
|
464
|
+
|
|
465
|
+
self.ensure_database( )
|
|
466
|
+
sql = f'''
|
|
467
|
+
INSERT INTO "{self.table_name}"
|
|
468
|
+
(
|
|
469
|
+
"created_on",
|
|
470
|
+
"cause",
|
|
471
|
+
"module",
|
|
472
|
+
"method",
|
|
473
|
+
"message",
|
|
474
|
+
"info",
|
|
475
|
+
"trace"
|
|
476
|
+
)
|
|
477
|
+
VALUES
|
|
478
|
+
(
|
|
479
|
+
?,
|
|
480
|
+
?,
|
|
481
|
+
?,
|
|
482
|
+
?,
|
|
483
|
+
?,
|
|
484
|
+
?,
|
|
485
|
+
?
|
|
486
|
+
)
|
|
487
|
+
'''
|
|
488
|
+
|
|
489
|
+
values = (datetime.utcnow( ).strftime( '%Y-%m-%d %H:%M:%S' ),
|
|
490
|
+
self.truncate( error.cause, 120 ),
|
|
491
|
+
self.truncate( error.module, 120 ),
|
|
492
|
+
self.truncate( error.method, 180 ),
|
|
493
|
+
self.truncate( error.message,
|
|
494
|
+
get_config_int( 'MAX_LOG_MESSAGE_CHARS', 1000 ) ),
|
|
495
|
+
self.truncate( error.info, get_config_int( 'MAX_LOG_TRACE_CHARS', 4000 ) ),
|
|
496
|
+
self.truncate( error.trace, get_config_int( 'MAX_LOG_TRACE_CHARS', 4000 ) ))
|
|
497
|
+
|
|
498
|
+
with sqlite3.connect( self.path ) as connection:
|
|
499
|
+
cursor = connection.execute( sql, values )
|
|
500
|
+
connection.commit( )
|
|
501
|
+
row_id = int( cursor.lastrowid or 0 )
|
|
502
|
+
|
|
503
|
+
self.purge_old_logs( )
|
|
504
|
+
return row_id
|
|
505
|
+
except Exception:
|
|
506
|
+
return 0
|
|
507
|
+
|
|
508
|
+
def purge_old_logs( self, retention_days: int = None ) -> int:
|
|
509
|
+
"""Delete log rows older than the configured retention period.
|
|
510
|
+
|
|
511
|
+
Purpose:
|
|
512
|
+
Support the prototype data-handling requirement by keeping local diagnostic logs short
|
|
513
|
+
lived. The method deletes rows whose ``created_on`` timestamp is older than the configured
|
|
514
|
+
retention window. If retention is set to zero or a negative value, no purge is performed.
|
|
515
|
+
|
|
516
|
+
Args:
|
|
517
|
+
retention_days (int): Optional retention window in days. When omitted, the value is read
|
|
518
|
+
from ``cfg.LOG_RETENTION_DAYS`` and defaults to seven days.
|
|
519
|
+
|
|
520
|
+
Returns:
|
|
521
|
+
int: Number of deleted rows, or ``0`` when no rows are deleted or purge fails.
|
|
522
|
+
"""
|
|
523
|
+
try:
|
|
524
|
+
self.ensure_database( )
|
|
525
|
+
days = retention_days if retention_days is not None else get_config_int(
|
|
526
|
+
'LOG_RETENTION_DAYS', 7 )
|
|
527
|
+
|
|
528
|
+
if days <= 0:
|
|
529
|
+
return 0
|
|
530
|
+
|
|
531
|
+
cutoff = datetime.utcnow( ) - timedelta( days=days )
|
|
532
|
+
sql = f'DELETE FROM "{self.table_name}" WHERE "created_on" < ?'
|
|
533
|
+
|
|
534
|
+
with sqlite3.connect( self.path ) as connection:
|
|
535
|
+
cursor = connection.execute( sql, (cutoff.strftime( '%Y-%m-%d %H:%M:%S' ),) )
|
|
536
|
+
connection.commit( )
|
|
537
|
+
return int( cursor.rowcount or 0 )
|
|
538
|
+
except Exception:
|
|
539
|
+
return 0
|
|
540
|
+
|
|
541
|
+
def log_error( error: Exception, heading: str = None, cause: str = None,
|
|
542
|
+
method: str = None, module: str = None ) -> Error:
|
|
543
|
+
"""Wrap and log an exception using the configured Fiddy error database.
|
|
544
|
+
|
|
545
|
+
Purpose:
|
|
546
|
+
Create an ``Error`` object and persist it in one step. The wrapped error object is returned
|
|
547
|
+
so callers can continue to inspect or display structured metadata after logging.
|
|
548
|
+
|
|
549
|
+
Args:
|
|
550
|
+
error (Exception): Source exception being wrapped and logged.
|
|
551
|
+
heading (str): Optional user-facing heading.
|
|
552
|
+
cause (str): Optional component, class, or module purpose that caused the error.
|
|
553
|
+
method (str): Optional stable method or function signature where the error occurred.
|
|
554
|
+
module (str): Optional module where the error occurred.
|
|
555
|
+
|
|
556
|
+
Returns:
|
|
557
|
+
Error: Wrapped and sanitized error object.
|
|
558
|
+
"""
|
|
559
|
+
exception = Error( error=error, heading=heading, cause=cause,
|
|
560
|
+
method=method, module=module )
|
|
561
|
+
|
|
562
|
+
Logger( ).write( exception )
|
|
563
|
+
return exception
|
boogr/default_icon.ico
ADDED
|
Binary file
|