osbot-utils 1.7.7__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.
- osbot_utils/__init__.py +1 -0
- osbot_utils/base_classes/Cache_Pickle.py +129 -0
- osbot_utils/base_classes/Kwargs_To_Disk.py +27 -0
- osbot_utils/base_classes/Kwargs_To_Self.py +308 -0
- osbot_utils/base_classes/Type_Safe__List.py +14 -0
- osbot_utils/base_classes/__init__.py +0 -0
- osbot_utils/context_managers/__init__.py +0 -0
- osbot_utils/context_managers/capture_duration.py +33 -0
- osbot_utils/decorators/__init__.py +0 -0
- osbot_utils/decorators/classes/__init__.py +0 -0
- osbot_utils/decorators/classes/singleton.py +9 -0
- osbot_utils/decorators/lists/__init__.py +0 -0
- osbot_utils/decorators/lists/filter_list.py +12 -0
- osbot_utils/decorators/lists/group_by.py +21 -0
- osbot_utils/decorators/lists/index_by.py +27 -0
- osbot_utils/decorators/methods/__init__.py +0 -0
- osbot_utils/decorators/methods/cache.py +19 -0
- osbot_utils/decorators/methods/cache_on_function.py +56 -0
- osbot_utils/decorators/methods/cache_on_self.py +78 -0
- osbot_utils/decorators/methods/cache_on_tmp.py +71 -0
- osbot_utils/decorators/methods/capture_exception.py +37 -0
- osbot_utils/decorators/methods/capture_status.py +20 -0
- osbot_utils/decorators/methods/catch.py +13 -0
- osbot_utils/decorators/methods/context.py +11 -0
- osbot_utils/decorators/methods/depreciated.py +79 -0
- osbot_utils/decorators/methods/function_type_check.py +62 -0
- osbot_utils/decorators/methods/obj_as_context.py +6 -0
- osbot_utils/decorators/methods/remove_return_value.py +22 -0
- osbot_utils/decorators/methods/required_fields.py +19 -0
- osbot_utils/fluent/Fluent_Dict.py +19 -0
- osbot_utils/fluent/Fluent_List.py +44 -0
- osbot_utils/fluent/__init__.py +1 -0
- osbot_utils/graphs/__init__.py +0 -0
- osbot_utils/graphs/mermaid/Mermaid.py +75 -0
- osbot_utils/graphs/mermaid/Mermaid__Edge.py +49 -0
- osbot_utils/graphs/mermaid/Mermaid__Graph.py +93 -0
- osbot_utils/graphs/mermaid/Mermaid__Node.py +69 -0
- osbot_utils/graphs/mermaid/Mermaid__Renderer.py +54 -0
- osbot_utils/graphs/mermaid/configs/Mermaid__Edge__Config.py +7 -0
- osbot_utils/graphs/mermaid/configs/Mermaid__Node__Config.py +9 -0
- osbot_utils/graphs/mermaid/configs/Mermaid__Render__Config.py +7 -0
- osbot_utils/graphs/mermaid/examples/Mermaid_Examples__FlowChart.py +98 -0
- osbot_utils/graphs/mermaid/models/Mermaid__Diagram_Direction.py +9 -0
- osbot_utils/graphs/mermaid/models/Mermaid__Diagram__Type.py +17 -0
- osbot_utils/graphs/mermaid/models/Mermaid__Node__Shape.py +30 -0
- osbot_utils/graphs/mgraph/MGraph.py +53 -0
- osbot_utils/graphs/mgraph/MGraph__Config.py +7 -0
- osbot_utils/graphs/mgraph/MGraph__Data.py +139 -0
- osbot_utils/graphs/mgraph/MGraph__Edge.py +27 -0
- osbot_utils/graphs/mgraph/MGraph__Node.py +33 -0
- osbot_utils/graphs/mgraph/MGraph__Random_Graphs.py +27 -0
- osbot_utils/graphs/mgraph/MGraph__Serializer.py +43 -0
- osbot_utils/graphs/mgraph/MGraphs.py +17 -0
- osbot_utils/graphs/mgraph/__init__.py +0 -0
- osbot_utils/helpers/CPrint.py +98 -0
- osbot_utils/helpers/Dict_To_Attr.py +7 -0
- osbot_utils/helpers/Local_Cache.py +111 -0
- osbot_utils/helpers/Local_Caches.py +54 -0
- osbot_utils/helpers/Print_Table.py +369 -0
- osbot_utils/helpers/Python_Audit.py +45 -0
- osbot_utils/helpers/Random_Seed.py +27 -0
- osbot_utils/helpers/SCP.py +58 -0
- osbot_utils/helpers/SSH.py +151 -0
- osbot_utils/helpers/Type_Registry.py +16 -0
- osbot_utils/helpers/__init__.py +0 -0
- osbot_utils/helpers/ast/Ast.py +35 -0
- osbot_utils/helpers/ast/Ast_Base.py +124 -0
- osbot_utils/helpers/ast/Ast_Data.py +28 -0
- osbot_utils/helpers/ast/Ast_Load.py +62 -0
- osbot_utils/helpers/ast/Ast_Merge.py +26 -0
- osbot_utils/helpers/ast/Ast_Node.py +117 -0
- osbot_utils/helpers/ast/Ast_Visit.py +85 -0
- osbot_utils/helpers/ast/Call_Tree.py +38 -0
- osbot_utils/helpers/ast/__init__.py +145 -0
- osbot_utils/helpers/ast/nodes/Ast_Add.py +6 -0
- osbot_utils/helpers/ast/nodes/Ast_Alias.py +6 -0
- osbot_utils/helpers/ast/nodes/Ast_And.py +6 -0
- osbot_utils/helpers/ast/nodes/Ast_Argument.py +7 -0
- osbot_utils/helpers/ast/nodes/Ast_Arguments.py +10 -0
- osbot_utils/helpers/ast/nodes/Ast_Assert.py +7 -0
- osbot_utils/helpers/ast/nodes/Ast_Assign.py +8 -0
- osbot_utils/helpers/ast/nodes/Ast_Attribute.py +9 -0
- osbot_utils/helpers/ast/nodes/Ast_Aug_Assign.py +9 -0
- osbot_utils/helpers/ast/nodes/Ast_Bin_Op.py +8 -0
- osbot_utils/helpers/ast/nodes/Ast_Bool_Op.py +7 -0
- osbot_utils/helpers/ast/nodes/Ast_Break.py +7 -0
- osbot_utils/helpers/ast/nodes/Ast_Call.py +17 -0
- osbot_utils/helpers/ast/nodes/Ast_Class_Def.py +9 -0
- osbot_utils/helpers/ast/nodes/Ast_Compare.py +9 -0
- osbot_utils/helpers/ast/nodes/Ast_Comprehension.py +10 -0
- osbot_utils/helpers/ast/nodes/Ast_Constant.py +6 -0
- osbot_utils/helpers/ast/nodes/Ast_Continue.py +7 -0
- osbot_utils/helpers/ast/nodes/Ast_Dict.py +8 -0
- osbot_utils/helpers/ast/nodes/Ast_Eq.py +6 -0
- osbot_utils/helpers/ast/nodes/Ast_Except_Handler.py +9 -0
- osbot_utils/helpers/ast/nodes/Ast_Expr.py +7 -0
- osbot_utils/helpers/ast/nodes/Ast_For.py +10 -0
- osbot_utils/helpers/ast/nodes/Ast_Function_Def.py +17 -0
- osbot_utils/helpers/ast/nodes/Ast_Generator_Exp.py +8 -0
- osbot_utils/helpers/ast/nodes/Ast_Gt.py +7 -0
- osbot_utils/helpers/ast/nodes/Ast_GtE.py +7 -0
- osbot_utils/helpers/ast/nodes/Ast_If.py +9 -0
- osbot_utils/helpers/ast/nodes/Ast_If_Exp.py +9 -0
- osbot_utils/helpers/ast/nodes/Ast_Import.py +7 -0
- osbot_utils/helpers/ast/nodes/Ast_Import_From.py +7 -0
- osbot_utils/helpers/ast/nodes/Ast_In.py +6 -0
- osbot_utils/helpers/ast/nodes/Ast_Is.py +6 -0
- osbot_utils/helpers/ast/nodes/Ast_Is_Not.py +7 -0
- osbot_utils/helpers/ast/nodes/Ast_Keyword.py +8 -0
- osbot_utils/helpers/ast/nodes/Ast_Lambda.py +8 -0
- osbot_utils/helpers/ast/nodes/Ast_List.py +8 -0
- osbot_utils/helpers/ast/nodes/Ast_List_Comp.py +8 -0
- osbot_utils/helpers/ast/nodes/Ast_Load.py +6 -0
- osbot_utils/helpers/ast/nodes/Ast_Lt.py +7 -0
- osbot_utils/helpers/ast/nodes/Ast_LtE.py +7 -0
- osbot_utils/helpers/ast/nodes/Ast_Mod.py +6 -0
- osbot_utils/helpers/ast/nodes/Ast_Module.py +20 -0
- osbot_utils/helpers/ast/nodes/Ast_Mult.py +6 -0
- osbot_utils/helpers/ast/nodes/Ast_Name.py +6 -0
- osbot_utils/helpers/ast/nodes/Ast_Not.py +7 -0
- osbot_utils/helpers/ast/nodes/Ast_Not_Eq.py +7 -0
- osbot_utils/helpers/ast/nodes/Ast_Not_In.py +6 -0
- osbot_utils/helpers/ast/nodes/Ast_Or.py +7 -0
- osbot_utils/helpers/ast/nodes/Ast_Pass.py +7 -0
- osbot_utils/helpers/ast/nodes/Ast_Pow.py +7 -0
- osbot_utils/helpers/ast/nodes/Ast_Raise.py +8 -0
- osbot_utils/helpers/ast/nodes/Ast_Return.py +7 -0
- osbot_utils/helpers/ast/nodes/Ast_Set.py +7 -0
- osbot_utils/helpers/ast/nodes/Ast_Slice.py +8 -0
- osbot_utils/helpers/ast/nodes/Ast_Starred.py +8 -0
- osbot_utils/helpers/ast/nodes/Ast_Store.py +7 -0
- osbot_utils/helpers/ast/nodes/Ast_Sub.py +7 -0
- osbot_utils/helpers/ast/nodes/Ast_Subscript.py +8 -0
- osbot_utils/helpers/ast/nodes/Ast_Try.py +9 -0
- osbot_utils/helpers/ast/nodes/Ast_Tuple.py +9 -0
- osbot_utils/helpers/ast/nodes/Ast_Unary_Op.py +7 -0
- osbot_utils/helpers/ast/nodes/Ast_While.py +8 -0
- osbot_utils/helpers/ast/nodes/Ast_With.py +7 -0
- osbot_utils/helpers/ast/nodes/Ast_With_Item.py +7 -0
- osbot_utils/helpers/ast/nodes/Ast_Yield.py +7 -0
- osbot_utils/helpers/ast/nodes/__init__.py +0 -0
- osbot_utils/helpers/html/Dict_To_Css.py +20 -0
- osbot_utils/helpers/html/Dict_To_Html.py +59 -0
- osbot_utils/helpers/html/Dict_To_Tags.py +88 -0
- osbot_utils/helpers/html/Html_To_Dict.py +75 -0
- osbot_utils/helpers/html/Html_To_Tag.py +20 -0
- osbot_utils/helpers/html/Tag__Base.py +91 -0
- osbot_utils/helpers/html/Tag__Body.py +5 -0
- osbot_utils/helpers/html/Tag__Div.py +5 -0
- osbot_utils/helpers/html/Tag__H.py +9 -0
- osbot_utils/helpers/html/Tag__HR.py +5 -0
- osbot_utils/helpers/html/Tag__Head.py +32 -0
- osbot_utils/helpers/html/Tag__Html.py +42 -0
- osbot_utils/helpers/html/Tag__Link.py +17 -0
- osbot_utils/helpers/html/Tag__Style.py +25 -0
- osbot_utils/helpers/html/__init__.py +0 -0
- osbot_utils/helpers/pubsub/Event__Queue.py +95 -0
- osbot_utils/helpers/pubsub/PubSub__Client.py +53 -0
- osbot_utils/helpers/pubsub/PubSub__Room.py +13 -0
- osbot_utils/helpers/pubsub/PubSub__Server.py +94 -0
- osbot_utils/helpers/pubsub/PubSub__Sqlite.py +24 -0
- osbot_utils/helpers/pubsub/__init__.py +0 -0
- osbot_utils/helpers/pubsub/schemas/Schema__Event.py +15 -0
- osbot_utils/helpers/pubsub/schemas/Schema__Event__Connect.py +7 -0
- osbot_utils/helpers/pubsub/schemas/Schema__Event__Disconnect.py +7 -0
- osbot_utils/helpers/pubsub/schemas/Schema__Event__Join_Room.py +8 -0
- osbot_utils/helpers/pubsub/schemas/Schema__Event__Leave_Room.py +8 -0
- osbot_utils/helpers/pubsub/schemas/Schema__Event__Message.py +7 -0
- osbot_utils/helpers/pubsub/schemas/Schema__PubSub__Client.py +8 -0
- osbot_utils/helpers/pubsub/schemas/__init__.py +0 -0
- osbot_utils/helpers/sqlite/Capture_Sqlite_Error.py +51 -0
- osbot_utils/helpers/sqlite/Sqlite__Cursor.py +87 -0
- osbot_utils/helpers/sqlite/Sqlite__Database.py +137 -0
- osbot_utils/helpers/sqlite/Sqlite__Field.py +70 -0
- osbot_utils/helpers/sqlite/Sqlite__Globals.py +5 -0
- osbot_utils/helpers/sqlite/Sqlite__Table.py +293 -0
- osbot_utils/helpers/sqlite/Sqlite__Table__Create.py +96 -0
- osbot_utils/helpers/sqlite/Temp_Sqlite__Database__Disk.py +17 -0
- osbot_utils/helpers/sqlite/Temp_Sqlite__Table.py +23 -0
- osbot_utils/helpers/sqlite/__init__.py +0 -0
- osbot_utils/helpers/sqlite/domains/Sqlite__Cache__Requests.py +214 -0
- osbot_utils/helpers/sqlite/domains/Sqlite__Cache__Requests__Patch.py +63 -0
- osbot_utils/helpers/sqlite/domains/Sqlite__DB__Files.py +23 -0
- osbot_utils/helpers/sqlite/domains/Sqlite__DB__Graph.py +47 -0
- osbot_utils/helpers/sqlite/domains/Sqlite__DB__Json.py +83 -0
- osbot_utils/helpers/sqlite/domains/Sqlite__DB__Local.py +20 -0
- osbot_utils/helpers/sqlite/domains/Sqlite__DB__Requests.py +39 -0
- osbot_utils/helpers/sqlite/domains/__init__.py +0 -0
- osbot_utils/helpers/sqlite/domains/schemas/Schema__Table__Requests.py +12 -0
- osbot_utils/helpers/sqlite/domains/schemas/__init__.py +0 -0
- osbot_utils/helpers/sqlite/models/Sqlite__Field__Type.py +37 -0
- osbot_utils/helpers/sqlite/models/__init__.py +0 -0
- osbot_utils/helpers/sqlite/sample_data/Sqlite__Sample_Data__Chinook.py +116 -0
- osbot_utils/helpers/sqlite/sample_data/__init__.py +0 -0
- osbot_utils/helpers/sqlite/sql_builder/SQL_Builder.py +159 -0
- osbot_utils/helpers/sqlite/sql_builder/SQL_Builder__Select.py +12 -0
- osbot_utils/helpers/sqlite/sql_builder/__init__.py +0 -0
- osbot_utils/helpers/sqlite/tables/Sqlite__Table__Config.py +63 -0
- osbot_utils/helpers/sqlite/tables/Sqlite__Table__Edges.py +46 -0
- osbot_utils/helpers/sqlite/tables/Sqlite__Table__Files.py +45 -0
- osbot_utils/helpers/sqlite/tables/Sqlite__Table__Nodes.py +52 -0
- osbot_utils/helpers/sqlite/tables/__init__.py +0 -0
- osbot_utils/helpers/trace/Trace_Call.py +120 -0
- osbot_utils/helpers/trace/Trace_Call__Config.py +94 -0
- osbot_utils/helpers/trace/Trace_Call__Graph.py +26 -0
- osbot_utils/helpers/trace/Trace_Call__Handler.py +215 -0
- osbot_utils/helpers/trace/Trace_Call__Print_Lines.py +85 -0
- osbot_utils/helpers/trace/Trace_Call__Print_Traces.py +170 -0
- osbot_utils/helpers/trace/Trace_Call__Stack.py +166 -0
- osbot_utils/helpers/trace/Trace_Call__Stack_Node.py +59 -0
- osbot_utils/helpers/trace/Trace_Call__Stats.py +71 -0
- osbot_utils/helpers/trace/Trace_Call__View_Model.py +75 -0
- osbot_utils/helpers/trace/Trace_Files.py +33 -0
- osbot_utils/helpers/trace/__init__.py +0 -0
- osbot_utils/testing/Catch.py +54 -0
- osbot_utils/testing/Duration.py +69 -0
- osbot_utils/testing/Hook_Method.py +118 -0
- osbot_utils/testing/Log_To_Queue.py +46 -0
- osbot_utils/testing/Log_To_String.py +37 -0
- osbot_utils/testing/Logging.py +81 -0
- osbot_utils/testing/Patch_Print.py +52 -0
- osbot_utils/testing/Profiler.py +89 -0
- osbot_utils/testing/Stderr.py +19 -0
- osbot_utils/testing/Stdout.py +19 -0
- osbot_utils/testing/Temp_File.py +46 -0
- osbot_utils/testing/Temp_Folder.py +114 -0
- osbot_utils/testing/Temp_Sys_Path.py +13 -0
- osbot_utils/testing/Temp_Web_Server.py +83 -0
- osbot_utils/testing/Temp_Zip.py +45 -0
- osbot_utils/testing/Temp_Zip_In_Memory.py +90 -0
- osbot_utils/testing/Unit_Test.py +34 -0
- osbot_utils/testing/Unzip_File.py +30 -0
- osbot_utils/testing/__init__.py +0 -0
- osbot_utils/utils/Assert.py +52 -0
- osbot_utils/utils/Call_Stack.py +187 -0
- osbot_utils/utils/Csv.py +32 -0
- osbot_utils/utils/Dev.py +47 -0
- osbot_utils/utils/Exceptions.py +7 -0
- osbot_utils/utils/Files.py +528 -0
- osbot_utils/utils/Functions.py +113 -0
- osbot_utils/utils/Http.py +136 -0
- osbot_utils/utils/Int.py +6 -0
- osbot_utils/utils/Json.py +171 -0
- osbot_utils/utils/Json_Cache.py +59 -0
- osbot_utils/utils/Lists.py +198 -0
- osbot_utils/utils/Misc.py +496 -0
- osbot_utils/utils/Objects.py +341 -0
- osbot_utils/utils/Png.py +29 -0
- osbot_utils/utils/Process.py +73 -0
- osbot_utils/utils/Python_Logger.py +301 -0
- osbot_utils/utils/Status.py +79 -0
- osbot_utils/utils/Str.py +63 -0
- osbot_utils/utils/Version.py +16 -0
- osbot_utils/utils/Zip.py +97 -0
- osbot_utils/utils/__init__.py +16 -0
- osbot_utils/version +1 -0
- osbot_utils-1.7.7.dist-info/LICENSE +201 -0
- osbot_utils-1.7.7.dist-info/METADATA +46 -0
- osbot_utils-1.7.7.dist-info/RECORD +260 -0
- osbot_utils-1.7.7.dist-info/WHEEL +4 -0
@@ -0,0 +1,63 @@
|
|
1
|
+
import types
|
2
|
+
|
3
|
+
from osbot_utils.helpers.sqlite.domains.Sqlite__Cache__Requests import Sqlite__Cache__Requests
|
4
|
+
from osbot_utils.utils.Dev import pprint
|
5
|
+
from osbot_utils.utils.Misc import random_text
|
6
|
+
|
7
|
+
|
8
|
+
class Sqlite__Cache__Requests__Patch(Sqlite__Cache__Requests):
|
9
|
+
db_name : str = random_text('requests_cache_')
|
10
|
+
table_name : str = random_text('requests_table_')
|
11
|
+
pickle_response : bool = True
|
12
|
+
target_function : types.FunctionType
|
13
|
+
target_class : object
|
14
|
+
target_function_name: str
|
15
|
+
|
16
|
+
def __init__(self, db_path=None):
|
17
|
+
super().__init__(db_path=db_path, db_name=self.db_name, table_name=self.table_name)
|
18
|
+
|
19
|
+
def __enter__(self):
|
20
|
+
self.patch_apply()
|
21
|
+
return self
|
22
|
+
|
23
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
24
|
+
self.patch_restore()
|
25
|
+
return
|
26
|
+
|
27
|
+
def delete(self):
|
28
|
+
return self.sqlite_requests.delete()
|
29
|
+
|
30
|
+
def proxy_method(self, *args, **kwargs):
|
31
|
+
request_data = self.request_data (*args, **kwargs)
|
32
|
+
target_kwargs = self.target_kwargs(*args, **kwargs)
|
33
|
+
target_args = self.target_args (*args, **kwargs)
|
34
|
+
|
35
|
+
invoke_kwargs = dict(target = self.target_function,
|
36
|
+
target_args = target_args ,
|
37
|
+
target_kwargs = target_kwargs ,
|
38
|
+
request_data = request_data )
|
39
|
+
|
40
|
+
return self.invoke_with_cache(**invoke_kwargs)
|
41
|
+
|
42
|
+
def patch_apply(self):
|
43
|
+
if (type(self.target_class) is object or
|
44
|
+
self.target_function is None or
|
45
|
+
self.target_function_name == '' ):
|
46
|
+
raise ValueError('target_function, target_object and target_function_name must be set')
|
47
|
+
def proxy(*args, **kwargs):
|
48
|
+
return self.proxy_method(*args, **kwargs)
|
49
|
+
setattr(self.target_class, self.target_function_name, proxy)
|
50
|
+
return self
|
51
|
+
|
52
|
+
def patch_restore(self):
|
53
|
+
setattr(self.target_class, self.target_function_name, self.target_function)
|
54
|
+
|
55
|
+
def request_data(self, *args, **kwargs):
|
56
|
+
return {'args' : args ,
|
57
|
+
'kwargs': kwargs }
|
58
|
+
|
59
|
+
def target_args(self, *args, **kwargs):
|
60
|
+
return args
|
61
|
+
|
62
|
+
def target_kwargs(self, *args, **kwargs):
|
63
|
+
return kwargs
|
@@ -0,0 +1,23 @@
|
|
1
|
+
from osbot_utils.decorators.methods.cache_on_self import cache_on_self
|
2
|
+
from osbot_utils.helpers.sqlite.domains.Sqlite__DB__Local import Sqlite__DB__Local
|
3
|
+
from osbot_utils.helpers.sqlite.tables.Sqlite__Table__Files import Sqlite__Table__Files
|
4
|
+
|
5
|
+
|
6
|
+
class Sqlite__DB__Files(Sqlite__DB__Local):
|
7
|
+
|
8
|
+
def __init__(self, db_path=None, db_name=None):
|
9
|
+
super().__init__(db_path=db_path, db_name=db_name)
|
10
|
+
|
11
|
+
def add_file(self, path, contents=None, metadata=None):
|
12
|
+
return self.table_files().add_file(path, contents, metadata)
|
13
|
+
|
14
|
+
@cache_on_self
|
15
|
+
def table_files(self):
|
16
|
+
return Sqlite__Table__Files(database=self).setup()
|
17
|
+
|
18
|
+
def files(self):
|
19
|
+
return self.table_files().files()
|
20
|
+
|
21
|
+
def setup(self):
|
22
|
+
self.table_files()
|
23
|
+
return self
|
@@ -0,0 +1,47 @@
|
|
1
|
+
from osbot_utils.decorators.methods.cache_on_self import cache_on_self
|
2
|
+
from osbot_utils.helpers.sqlite.domains.Sqlite__DB__Local import Sqlite__DB__Local
|
3
|
+
from osbot_utils.helpers.sqlite.tables.Sqlite__Table__Edges import Sqlite__Table__Edges
|
4
|
+
from osbot_utils.helpers.sqlite.tables.Sqlite__Table__Nodes import Sqlite__Table__Nodes
|
5
|
+
|
6
|
+
|
7
|
+
class Sqlite__DB__Graph(Sqlite__DB__Local):
|
8
|
+
|
9
|
+
def __init__(self, db_path=None, db_name=None):
|
10
|
+
super().__init__(db_path=db_path, db_name=db_name)
|
11
|
+
|
12
|
+
def add_edge(self, source_key, target_key, value=None, properties=None):
|
13
|
+
new_edge = self.table_edges().add_edge(source_key, target_key, value, properties)
|
14
|
+
self.add_node(source_key) # assuming node_table.allow_duplicate_keys is set to False
|
15
|
+
self.add_node(target_key) # make sure there is a node for each part of the edge added
|
16
|
+
return new_edge
|
17
|
+
|
18
|
+
|
19
|
+
def add_node(self, key, value=None, properties=None):
|
20
|
+
return self.table_nodes().add_node(key, value, properties)
|
21
|
+
|
22
|
+
def clear(self):
|
23
|
+
self.table_edges().clear()
|
24
|
+
self.table_nodes().clear()
|
25
|
+
|
26
|
+
def edges(self):
|
27
|
+
return self.table_edges().edges()
|
28
|
+
|
29
|
+
def nodes(self):
|
30
|
+
return self.table_nodes().nodes()
|
31
|
+
|
32
|
+
def nodes_keys(self):
|
33
|
+
return self.table_nodes().keys()
|
34
|
+
|
35
|
+
@cache_on_self
|
36
|
+
def table_edges(self):
|
37
|
+
return Sqlite__Table__Edges(database=self).setup()
|
38
|
+
|
39
|
+
@cache_on_self
|
40
|
+
def table_nodes(self):
|
41
|
+
return Sqlite__Table__Nodes(database=self).setup()
|
42
|
+
|
43
|
+
def setup(self):
|
44
|
+
#self.table_config() # wire this up when I have a use case for it
|
45
|
+
self.table_nodes()
|
46
|
+
self.table_edges()
|
47
|
+
return self
|
@@ -0,0 +1,83 @@
|
|
1
|
+
from osbot_utils.base_classes.Kwargs_To_Self import Kwargs_To_Self
|
2
|
+
from osbot_utils.helpers.sqlite.Sqlite__Database import Sqlite__Database
|
3
|
+
from osbot_utils.helpers.sqlite.Sqlite__Table__Create import Sqlite__Table__Create
|
4
|
+
from osbot_utils.helpers.sqlite.models.Sqlite__Field__Type import Sqlite__Field__Type
|
5
|
+
|
6
|
+
|
7
|
+
class Sqlite__DB__Json(Kwargs_To_Self):
|
8
|
+
database : Sqlite__Database
|
9
|
+
table_create : Sqlite__Table__Create
|
10
|
+
table_name : str = 'new_db_table'
|
11
|
+
|
12
|
+
def __init__(self):
|
13
|
+
super().__init__()
|
14
|
+
self.table_create = Sqlite__Table__Create(self.table_name)
|
15
|
+
self.table_create.table.database = self.database
|
16
|
+
|
17
|
+
def create_fields_from_json_data(self, json_data):
|
18
|
+
for key,value in json_data.items():
|
19
|
+
self.table_create.add_field_with_type(key, type(value))
|
20
|
+
|
21
|
+
def create_table_from_schema(self, table_name, schema):
|
22
|
+
table_create = Sqlite__Table__Create(table_name)
|
23
|
+
table_create.table.database = self.database
|
24
|
+
for field_name,field_type in schema.items():
|
25
|
+
table_create.add_field_with_type(field_name, field_type)
|
26
|
+
|
27
|
+
if table_create.create_table():
|
28
|
+
return table_create.table
|
29
|
+
|
30
|
+
def create_table_from_json_data(self, json_data):
|
31
|
+
self.create_fields_from_json_data(json_data)
|
32
|
+
if self.table_create.create_table():
|
33
|
+
self.table_create.table.row_add_record(json_data)
|
34
|
+
return self.table_create.table
|
35
|
+
|
36
|
+
#def add_data_to_data_from_json_data(self, json_data):
|
37
|
+
|
38
|
+
def get_schema_from_json_data(self, json_data):
|
39
|
+
if type(json_data) is dict:
|
40
|
+
return self.get_schema_from_dict(json_data)
|
41
|
+
if type(json_data) is list:
|
42
|
+
return self.get_schema_from_list_of_dict(json_data)
|
43
|
+
|
44
|
+
def get_schema_from_list_of_dict(self, target):
|
45
|
+
if not isinstance(target, list):
|
46
|
+
raise ValueError("in get_schema_from_list_of_dict, the provided target is not a list")
|
47
|
+
|
48
|
+
overall_schema = {}
|
49
|
+
|
50
|
+
for item in target:
|
51
|
+
if not isinstance(item, dict):
|
52
|
+
continue # or raise an exception, depending on your needs
|
53
|
+
|
54
|
+
current_schema = self.get_schema_from_dict(item)
|
55
|
+
|
56
|
+
for key, current_type in current_schema.items():
|
57
|
+
if (key in overall_schema and # Check if key exists in the overall schema
|
58
|
+
overall_schema[key] != current_type and # Check if there's a type mismatch for the key
|
59
|
+
current_type != Sqlite__Field__Type.UNKNOWN and # Ensure current type is not UNKNOWN
|
60
|
+
overall_schema[key] != Sqlite__Field__Type.UNKNOWN ): # Ensure overall schema's type for the key is not UNKNOWN
|
61
|
+
message = f"Type conflict for field '{key}': {overall_schema[key]} vs {current_type}"
|
62
|
+
raise ValueError(message)
|
63
|
+
else:
|
64
|
+
overall_schema[key] = current_type # Update or add the key with its type to the overall schema
|
65
|
+
|
66
|
+
|
67
|
+
return overall_schema
|
68
|
+
|
69
|
+
def get_schema_from_dict(self, target):
|
70
|
+
schema = {}
|
71
|
+
type_map = Sqlite__Field__Type.type_map()
|
72
|
+
for key, value in target.items():
|
73
|
+
value_type = type(value)
|
74
|
+
field_type = type_map.get(value_type)
|
75
|
+
if field_type is None:
|
76
|
+
raise ValueError(f"in get_schema_from_dict, the value_type {value_type} from '{key} = {value}' is not supported by Sqlite__Field__Type")
|
77
|
+
schema[key] = field_type
|
78
|
+
return schema
|
79
|
+
|
80
|
+
|
81
|
+
|
82
|
+
|
83
|
+
|
@@ -0,0 +1,20 @@
|
|
1
|
+
from os import environ
|
2
|
+
from osbot_utils.helpers.sqlite.Sqlite__Database import Sqlite__Database
|
3
|
+
from osbot_utils.utils.Files import current_temp_folder, path_combine
|
4
|
+
from osbot_utils.utils.Misc import random_text
|
5
|
+
from osbot_utils.utils.Str import str_safe
|
6
|
+
|
7
|
+
ENV_NAME_PATH_LOCAL_DBS = 'PATH_LOCAL_DBS'
|
8
|
+
|
9
|
+
class Sqlite__DB__Local(Sqlite__Database):
|
10
|
+
db_name: str
|
11
|
+
|
12
|
+
def __init__(self, db_path=None, db_name=None):
|
13
|
+
self.db_name = db_name or random_text('db_local') + '.sqlite'
|
14
|
+
super().__init__(db_path=db_path or self.path_local_db())
|
15
|
+
|
16
|
+
def path_db_folder(self):
|
17
|
+
return environ.get(ENV_NAME_PATH_LOCAL_DBS) or current_temp_folder()
|
18
|
+
|
19
|
+
def path_local_db(self):
|
20
|
+
return path_combine(self.path_db_folder(), str_safe(self.db_name))
|
@@ -0,0 +1,39 @@
|
|
1
|
+
from osbot_utils.decorators.methods.cache_on_self import cache_on_self
|
2
|
+
from osbot_utils.helpers.sqlite.domains.Sqlite__DB__Local import Sqlite__DB__Local
|
3
|
+
from osbot_utils.helpers.sqlite.domains.schemas.Schema__Table__Requests import Schema__Table__Requests
|
4
|
+
from osbot_utils.utils.Misc import random_text
|
5
|
+
|
6
|
+
SQLITE_TABLE__REQUESTS = 'requests'
|
7
|
+
|
8
|
+
class Sqlite__DB__Requests(Sqlite__DB__Local):
|
9
|
+
table_name : str
|
10
|
+
table_schema: type
|
11
|
+
|
12
|
+
def __init__(self,db_path=None, db_name=None, table_name=None):
|
13
|
+
self.table_name = table_name or SQLITE_TABLE__REQUESTS
|
14
|
+
self.table_schema = Schema__Table__Requests
|
15
|
+
super().__init__(db_path=db_path, db_name=db_name)
|
16
|
+
# if not self.table_name:
|
17
|
+
# self.table_name = 'temp_table'
|
18
|
+
self.setup()
|
19
|
+
|
20
|
+
@cache_on_self
|
21
|
+
def table_requests(self):
|
22
|
+
return self.table(self.table_name)
|
23
|
+
|
24
|
+
def table_requests__create(self):
|
25
|
+
with self.table_requests() as _:
|
26
|
+
_.row_schema = self.table_schema # set the table_class
|
27
|
+
if _.exists() is False:
|
28
|
+
_.create() # create if it doesn't exist
|
29
|
+
_.index_create('request_hash') # add index to the request_hash field
|
30
|
+
return True
|
31
|
+
return False
|
32
|
+
|
33
|
+
def table_requests__reset(self):
|
34
|
+
self.table_requests().delete()
|
35
|
+
return self.table_requests__create()
|
36
|
+
|
37
|
+
def setup(self):
|
38
|
+
self.table_requests__create()
|
39
|
+
return self
|
File without changes
|
@@ -0,0 +1,12 @@
|
|
1
|
+
from osbot_utils.base_classes.Kwargs_To_Self import Kwargs_To_Self
|
2
|
+
|
3
|
+
class Schema__Table__Requests(Kwargs_To_Self):
|
4
|
+
request_hash : str
|
5
|
+
request_data : str
|
6
|
+
response_hash : str
|
7
|
+
response_data : str
|
8
|
+
response_bytes: bytes
|
9
|
+
cache_hits : int # todo: to implement
|
10
|
+
timestamp : int
|
11
|
+
latest : bool # todo: add native bool support to sqlite
|
12
|
+
comments : str
|
File without changes
|
@@ -0,0 +1,37 @@
|
|
1
|
+
from decimal import Decimal
|
2
|
+
from enum import Enum, auto
|
3
|
+
from types import NoneType
|
4
|
+
|
5
|
+
from osbot_utils.decorators.methods.cache import cache
|
6
|
+
|
7
|
+
|
8
|
+
class Sqlite__Field__Type(Enum):
|
9
|
+
BOOLEAN = bool
|
10
|
+
DECIMAL = Decimal
|
11
|
+
INTEGER = int
|
12
|
+
TEXT = str
|
13
|
+
BLOB = bytes
|
14
|
+
REAL = float
|
15
|
+
NUMERIC = 'numeric' # special case to have some support for using NUMERIC in the Create Table
|
16
|
+
UNKNOWN = NoneType # special type to handle cases when the type is not known # todo: handle this on the table creation stage
|
17
|
+
|
18
|
+
def __repr__(self):
|
19
|
+
return f'Sqlite__Field__Type.{self.name}'
|
20
|
+
|
21
|
+
def __str__(self):
|
22
|
+
return self.name
|
23
|
+
|
24
|
+
@classmethod
|
25
|
+
@cache
|
26
|
+
def type_map(cls):
|
27
|
+
type_map = {}
|
28
|
+
for member in cls:
|
29
|
+
if member.value not in [auto()]:
|
30
|
+
type_map[member.value] = member
|
31
|
+
return type_map
|
32
|
+
|
33
|
+
@classmethod
|
34
|
+
@cache
|
35
|
+
def enum_map(cls):
|
36
|
+
return {member.name: member.value for member in cls}
|
37
|
+
|
File without changes
|
@@ -0,0 +1,116 @@
|
|
1
|
+
from osbot_utils.base_classes.Kwargs_To_Self import Kwargs_To_Self
|
2
|
+
from osbot_utils.decorators.methods.cache_on_self import cache_on_self
|
3
|
+
from osbot_utils.helpers.sqlite.Sqlite__Database import Sqlite__Database
|
4
|
+
from osbot_utils.helpers.sqlite.Sqlite__Table import Sqlite__Table
|
5
|
+
from osbot_utils.helpers.sqlite.Sqlite__Table__Create import Sqlite__Table__Create
|
6
|
+
from osbot_utils.helpers.sqlite.domains.Sqlite__DB__Json import Sqlite__DB__Json
|
7
|
+
from osbot_utils.testing.Duration import duration, Duration
|
8
|
+
from osbot_utils.utils.Dev import pprint
|
9
|
+
from osbot_utils.utils.Files import current_temp_folder, path_combine, folder_create, file_exists, file_not_exists, \
|
10
|
+
file_contents
|
11
|
+
from osbot_utils.utils.Http import GET, GET_to_file
|
12
|
+
from osbot_utils.utils.Json import json_loads, json_from_file, json_dump, json_file_create, json_file_load
|
13
|
+
from osbot_utils.utils.Objects import obj_info
|
14
|
+
|
15
|
+
URL__CHINOOK_JSON = 'https://github.com/lerocha/chinook-database/releases/download/v1.4.5/ChinookData.json'
|
16
|
+
FILE_NAME__CHINOOK_DATA_JSON = 'ChinookData.json'
|
17
|
+
FILE_NAME__TABLES_SCHEMAS_FIELDS = 'chinook__tables_schemas_fields.json'
|
18
|
+
FOLDER_NAME__CHINOOK_DATA = 'chinook_data'
|
19
|
+
FOLDER_NAME__SQLITE_DATA_SETS = '_sqlite_data_sets'
|
20
|
+
PATH__DB__TESTS = '/tmp/db-tests'
|
21
|
+
PATH__DB__CHINOOK = '/tmp/db-tests/test-chinook.db'
|
22
|
+
TABLE_NAME__CHINOOK = 'chinook_data'
|
23
|
+
|
24
|
+
class Sqlite__Sample_Data__Chinook(Kwargs_To_Self):
|
25
|
+
url_chinook_database_json : str = URL__CHINOOK_JSON
|
26
|
+
path_local_db : str = PATH__DB__CHINOOK
|
27
|
+
table_name : str = TABLE_NAME__CHINOOK
|
28
|
+
json_db : Sqlite__DB__Json
|
29
|
+
|
30
|
+
|
31
|
+
|
32
|
+
def chinook_data_as_json(self):
|
33
|
+
path_chinook_data_as_json = self.path_chinook_data_as_json()
|
34
|
+
if file_not_exists(path_chinook_data_as_json):
|
35
|
+
GET_to_file(self.url_chinook_database_json, path_chinook_data_as_json)
|
36
|
+
return json_from_file(path_chinook_data_as_json)
|
37
|
+
|
38
|
+
# def create_table_from_data(self):
|
39
|
+
# chinook_data = self.chinook_data_as_json()
|
40
|
+
# table_creator = Sqlite__Table__Create(table_name=self.table_name)
|
41
|
+
# table = table_creator.table
|
42
|
+
# table_creator.add_fields__text("name", "value").create_table()
|
43
|
+
#
|
44
|
+
# cursor = table.cursor()
|
45
|
+
# assert len(chinook_data) == 11
|
46
|
+
# for key, items in chinook_data.items():
|
47
|
+
# name = key
|
48
|
+
# value = json_dump(items)
|
49
|
+
# table.row_add(dict(name=name, value=value))
|
50
|
+
#
|
51
|
+
# cursor.commit()
|
52
|
+
# return table
|
53
|
+
|
54
|
+
def create_tables(self):
|
55
|
+
self.create_tables_from_schema()
|
56
|
+
chinook_data = self.chinook_data_as_json()
|
57
|
+
for table_name, rows in chinook_data.items():
|
58
|
+
table = self.json_db.database.table(table_name)
|
59
|
+
table.rows_add(rows)
|
60
|
+
|
61
|
+
|
62
|
+
|
63
|
+
def create_tables_from_schema(self):
|
64
|
+
schemas = self.tables_schemas_fields_from_data()
|
65
|
+
for table_name, table_schema in schemas.items():
|
66
|
+
self.json_db.create_table_from_schema(table_name, table_schema)
|
67
|
+
return self
|
68
|
+
|
69
|
+
def database(self): # todo: need to refactor this code based from the creation of the database to the use of it
|
70
|
+
return self.json_db.database
|
71
|
+
|
72
|
+
@cache_on_self
|
73
|
+
def load_db_from_disk(self):
|
74
|
+
db_chinook = Sqlite__Database(db_path=PATH__DB__CHINOOK, auto_schema_row=True) # set the auto_schema_row so that we have a row_schema defined for all tables
|
75
|
+
return db_chinook
|
76
|
+
# db_chinook.connect()
|
77
|
+
# return db_chinook.table(self.table_name)
|
78
|
+
|
79
|
+
def path_chinook_data_as_json(self):
|
80
|
+
return path_combine(self.path_chinook_data(), FILE_NAME__CHINOOK_DATA_JSON)
|
81
|
+
|
82
|
+
def path_chinook_data(self):
|
83
|
+
path_chinook_data = path_combine(self.path_sqlite_sample_data_sets(), FOLDER_NAME__CHINOOK_DATA)
|
84
|
+
return folder_create(path_chinook_data)
|
85
|
+
|
86
|
+
def path_sqlite_sample_data_sets(self): # todo: refactor to sqlite_sample_data_sets helper class
|
87
|
+
path_data_sets = path_combine(current_temp_folder(), FOLDER_NAME__SQLITE_DATA_SETS)
|
88
|
+
return folder_create(path_data_sets)
|
89
|
+
|
90
|
+
def path_tables_schemas_fields(self):
|
91
|
+
return path_combine(self.path_chinook_data(), FILE_NAME__TABLES_SCHEMAS_FIELDS)
|
92
|
+
|
93
|
+
def table__genre(self):
|
94
|
+
return self.load_db_from_disk().table('Genre') # todo: refactor to sqlite_sample_data_sets helper class
|
95
|
+
|
96
|
+
def table__track(self):
|
97
|
+
return self.load_db_from_disk().table('Track') # todo: refactor to sqlite_sample_data_sets helper class
|
98
|
+
|
99
|
+
# def tables(self):
|
100
|
+
# return self.load_db_from_disk().tables()
|
101
|
+
|
102
|
+
def tables_schemas_fields_from_data(self):
|
103
|
+
path_tables_schemas_fields = self.path_tables_schemas_fields()
|
104
|
+
if file_not_exists(path_tables_schemas_fields):
|
105
|
+
tables_schemas = {}
|
106
|
+
for name, data in self.chinook_data_as_json().items():
|
107
|
+
table_schema = self.json_db.get_schema_from_json_data(data)
|
108
|
+
tables_schemas[name] = table_schema
|
109
|
+
json_file_create(tables_schemas, path=path_tables_schemas_fields)
|
110
|
+
|
111
|
+
return json_file_load(path_tables_schemas_fields)
|
112
|
+
|
113
|
+
def save(self, path=PATH__DB__CHINOOK):
|
114
|
+
database = self.database()
|
115
|
+
database.save_to(path)
|
116
|
+
return True
|
File without changes
|
@@ -0,0 +1,159 @@
|
|
1
|
+
from osbot_utils.base_classes.Kwargs_To_Self import Kwargs_To_Self
|
2
|
+
from osbot_utils.helpers.sqlite.Sqlite__Globals import ROW_BASE_CLASS
|
3
|
+
from osbot_utils.helpers.sqlite.Sqlite__Table import Sqlite__Table
|
4
|
+
|
5
|
+
|
6
|
+
class SQL_Builder(Kwargs_To_Self):
|
7
|
+
table : Sqlite__Table
|
8
|
+
limit : int = None # set it to None to make it explict that the limit is not set
|
9
|
+
|
10
|
+
def validate_query_data(self):
|
11
|
+
if self.table.row_schema is None:
|
12
|
+
raise ValueError("in SQL_Builder, there was no row_schema defined in the mapped table")
|
13
|
+
|
14
|
+
def select_for_fields(self, field_names: list = None):
|
15
|
+
valid_fields = self.table.fields_names__cached()
|
16
|
+
if field_names is None:
|
17
|
+
field_names = valid_fields
|
18
|
+
elif isinstance(field_names, list) is False:
|
19
|
+
raise ValueError(f"in sql_query_for_fields, field_names must be a list, it was :{field_names}")
|
20
|
+
|
21
|
+
invalid_field_names = [name for name in field_names if name not in valid_fields] # If no valid field names are provided, raise an error or return a default query
|
22
|
+
if invalid_field_names: # If there are any invalid field names, raise an exception listing them
|
23
|
+
message = f"Invalid field names provided: {', '.join(invalid_field_names)}"
|
24
|
+
raise ValueError(message)
|
25
|
+
|
26
|
+
fields_str = ', '.join(field_names) # Construct the SQL query string
|
27
|
+
sql_query = f"SELECT {fields_str} FROM {self.table.table_name}{self.sql_limit()};" # Join the valid field names with commas
|
28
|
+
return sql_query
|
29
|
+
|
30
|
+
def command__delete_table(self):
|
31
|
+
return f'DELETE FROM {self.table.table_name}'
|
32
|
+
|
33
|
+
def command__delete_where(self, query_conditions):
|
34
|
+
self.validator().validate_query_fields(self.table, [],query_conditions) # todo: add method to validate_query_fields to handle just the query section (so that we don't need to provide a empty list for the return values)
|
35
|
+
target_table = self.table.table_name
|
36
|
+
where_fields = list(query_conditions.keys())
|
37
|
+
params = list(query_conditions.values())
|
38
|
+
where_clause = ' AND '.join([f"{field}=?" for field in where_fields]) # todo: refactor into method that just handle the WHERE statements
|
39
|
+
sql_query = f"DELETE FROM {target_table} WHERE {where_clause}" # todo: refactor into a method that creates the final statement from a number of other objects (or strings)
|
40
|
+
return sql_query, params
|
41
|
+
|
42
|
+
def command_for_insert(self, record):
|
43
|
+
valid_field_names = self.table.fields_names__cached()
|
44
|
+
if type(record) is dict:
|
45
|
+
if record:
|
46
|
+
field_names = record.keys()
|
47
|
+
params = list(record.values())
|
48
|
+
for field_name in field_names:
|
49
|
+
if field_name not in valid_field_names:
|
50
|
+
raise ValueError(f'in sql_command_for_insert, there was a field_name "{field_name}" that did exist in the current table')
|
51
|
+
|
52
|
+
columns = ', '.join(field_names) # Construct column names and placeholders
|
53
|
+
placeholders = ', '.join(['?' for _ in field_names])
|
54
|
+
sql_command = f'INSERT INTO {self.table.table_name} ({columns}) VALUES ({placeholders})' # Construct the SQL statement
|
55
|
+
return sql_command, params
|
56
|
+
|
57
|
+
def query_for_fields(self, field_names: list = None):
|
58
|
+
return self.select_for_fields(field_names)
|
59
|
+
|
60
|
+
def query_for_size(self):
|
61
|
+
return f'SELECT COUNT(*) as size FROM {self.table.table_name}'
|
62
|
+
|
63
|
+
def sql_limit(self):
|
64
|
+
if self.limit is not None:
|
65
|
+
return f" LIMIT {int(self.limit)}"
|
66
|
+
return ""
|
67
|
+
|
68
|
+
def query_select_fields_with_conditions(self, return_fields, query_conditions):
|
69
|
+
self.validator().validate_query_fields(self.table , return_fields, query_conditions)
|
70
|
+
target_table = self.table.table_name
|
71
|
+
if target_table and return_fields and query_conditions:
|
72
|
+
where_fields = list(query_conditions.keys())
|
73
|
+
params = list(query_conditions.values())
|
74
|
+
fields_to_return = ', '.join(return_fields) # Join the select_fields list into a comma-separated string
|
75
|
+
where_clause = ' AND '.join([f"{field}=?" for field in where_fields]) # Dynamically construct the WHERE clause based on condition_fields
|
76
|
+
sql_query = f"SELECT {fields_to_return} FROM {target_table} WHERE {where_clause}" # Construct the full SQL query
|
77
|
+
return sql_query, params
|
78
|
+
|
79
|
+
def query_for_select_rows_where(self, **kwargs):
|
80
|
+
valid_fields = self.table.fields__cached() # Get a list of valid field names from the cached schema
|
81
|
+
params = [] # Initialize an empty list to hold query parameters
|
82
|
+
where_clauses = [] # Initialize an empty list to hold parts of the WHERE clause
|
83
|
+
for field_name, query_value in kwargs.items(): # Iterate over each keyword argument and its value
|
84
|
+
if field_name not in valid_fields: # Check if the provided field name is valid
|
85
|
+
raise ValueError(f'in select_rows_where, the provided field is not valid: {field_name}')
|
86
|
+
params.append(query_value) # Append the query value to the parameters list
|
87
|
+
where_clauses.append(f"{field_name} = ?") # Append the corresponding WHERE clause part, using a placeholder for the value
|
88
|
+
where_clause = ' AND '.join(where_clauses) # Join the individual parts of the WHERE clause with 'AND'
|
89
|
+
|
90
|
+
sql_query = f"SELECT * FROM {self.table.table_name} WHERE {where_clause}" # Construct the full SQL query
|
91
|
+
return sql_query, params
|
92
|
+
|
93
|
+
def sql_query_update_with_conditions(self, update_fields, query_conditions):
|
94
|
+
update_keys = list(update_fields.keys()) # todo: refactor self.validate_query_fields to use a more generic value for these fields
|
95
|
+
condition_keys = list(query_conditions.keys())
|
96
|
+
self.validator().validate_query_fields(self.table, update_keys, query_conditions)
|
97
|
+
target_table = self.table.table_name
|
98
|
+
if target_table and update_fields and query_conditions:
|
99
|
+
update_clause = ', '.join([f"{key}=?" for key in update_keys])
|
100
|
+
where_clause = ' AND '.join([f"{field}=?" for field in condition_keys])
|
101
|
+
sql_query = f"UPDATE {target_table} SET {update_clause} WHERE {where_clause}"
|
102
|
+
|
103
|
+
# The parameters for the SQL execution must include both the update values and the condition values, in the correct order.
|
104
|
+
params = list(update_fields.values()) + list(query_conditions.values())
|
105
|
+
return sql_query, params
|
106
|
+
|
107
|
+
def validate_row_obj(self, row_obj):
|
108
|
+
field_types = self.table.fields_types__cached()
|
109
|
+
invalid_reason = ""
|
110
|
+
if self.table.row_schema:
|
111
|
+
if row_obj:
|
112
|
+
if issubclass(type(row_obj), ROW_BASE_CLASS):
|
113
|
+
for field_name, field_type in row_obj.__annotations__.items():
|
114
|
+
if field_name not in field_types:
|
115
|
+
invalid_reason = f'provided row_obj has a field that is not part of the current table: {field_name}'
|
116
|
+
break
|
117
|
+
|
118
|
+
if field_type != field_types[field_name]:
|
119
|
+
invalid_reason = f'provided row_obj has a field {field_name} that has a field type {field_type} that does not match the current tables type of that field: {field_types[field_name]}'
|
120
|
+
break
|
121
|
+
if invalid_reason == '':
|
122
|
+
for field_name, field_value in row_obj.__locals__().items():
|
123
|
+
if field_name not in field_types:
|
124
|
+
invalid_reason = f'provided row_obj has a field that is not part of the current table: {field_name}'
|
125
|
+
break
|
126
|
+
if type(field_value) != field_types.get(field_name):
|
127
|
+
invalid_reason = f'provided row_obj has a field {field_name} that has a field value {field_value} value that has a type {type(field_value)} that does not match the current tables type of that field: {field_types.get(field_name)}'
|
128
|
+
break
|
129
|
+
else:
|
130
|
+
invalid_reason = f'provided row_obj ({type(row_obj)}) is not a subclass of {ROW_BASE_CLASS}'
|
131
|
+
else:
|
132
|
+
invalid_reason = f'provided row_obj was None'
|
133
|
+
else:
|
134
|
+
invalid_reason = f'there is no row_schema defined for this table {self.table.table_name}'
|
135
|
+
return invalid_reason
|
136
|
+
|
137
|
+
def validator(self):
|
138
|
+
return SQL_Query__Validator()
|
139
|
+
|
140
|
+
class SQL_Query__Validator:
|
141
|
+
|
142
|
+
# todo: refactor this method to use a more generic value for these return_fields since it is already being used in two use cases: return fields and update fields
|
143
|
+
def validate_query_fields(self, table, return_fields, query_conditions):
|
144
|
+
target_table = table.table_name
|
145
|
+
valid_fields = table.fields_names__cached(include_star_field=True)
|
146
|
+
if target_table not in table.database.tables_names(include_sqlite_master=True):
|
147
|
+
raise ValueError(f'in validate_query_fields, invalid target_table name: "{target_table}"')
|
148
|
+
if type(return_fields) is not list:
|
149
|
+
raise ValueError(f'in validate_query_fields, return_fields value must be a list, and it was "{type(return_fields)}"')
|
150
|
+
for return_field in return_fields:
|
151
|
+
if return_field not in valid_fields:
|
152
|
+
raise ValueError(f'in validate_query_fields, invalid, invalid return_field: "{return_field}"')
|
153
|
+
if type(query_conditions) is not dict:
|
154
|
+
raise ValueError(f'in validate_query_fields, query_conditions value must be a dict, and it was "{type(query_conditions)}"')
|
155
|
+
for where_field in query_conditions.keys():
|
156
|
+
if where_field not in valid_fields:
|
157
|
+
raise ValueError(f'in validate_query_fields, invalid, invalid return_field: "{where_field}"')
|
158
|
+
|
159
|
+
return self
|
@@ -0,0 +1,12 @@
|
|
1
|
+
from osbot_utils.helpers.sqlite.sql_builder.SQL_Builder import SQL_Builder
|
2
|
+
|
3
|
+
|
4
|
+
class SQL_Builder__Select(SQL_Builder): # todo: to refactor SELECT methods into this class
|
5
|
+
pass
|
6
|
+
# def build(self):
|
7
|
+
# self.validate_query_data()
|
8
|
+
#
|
9
|
+
# return f"SELECT * FROM *"
|
10
|
+
|
11
|
+
# def validate_query_data(self):
|
12
|
+
# super().validate_query_data()
|
File without changes
|