From 175cc1302247f51b6c5481c9a07193a2346ce641 Mon Sep 17 00:00:00 2001 From: KyMAN <0kyman0@gmail.com> Date: Mon, 6 Jul 2026 07:12:34 +0200 Subject: [PATCH] feat(py): SQLite. --- JSON/I18N/AnP.i18n.espanol.json | 1 + .../AnP/Abstracts/AIInterpretersAbstract.py | 5 +- Python/AnP/Abstracts/BrowserAbstract.py | 196 +++++++++--------- Python/AnP/Abstracts/DatabaseAbstract.py | 70 +++++++ Python/AnP/Abstracts/FilesAbstract.py | 10 +- Python/AnP/Abstracts/HTTPServersAbstract.py | 5 +- Python/AnP/Abstracts/ModelAbstract.py | 4 +- .../AnP/Abstracts/WebSocketServersAbstract.py | 5 +- Python/AnP/Application/AnP.py | 5 + Python/AnP/DSLs/ScrapDSL.py | 1 + Python/AnP/Drivers/FilesDriver.py | 12 +- Python/AnP/Drivers/HTTPServerDriver.py | 3 +- Python/AnP/Drivers/MySQLDriver.py | 17 +- Python/AnP/Drivers/OllamaDriver.py | 4 +- Python/AnP/Drivers/PostgreSQLDriver.py | 17 ++ Python/AnP/Drivers/SQLServerDriver.py | 17 +- Python/AnP/Drivers/SQLiteDriver.py | 166 ++++++++++++++- Python/AnP/Drivers/SeleniumDriver.py | 157 ++++++++------ Python/AnP/Drivers/WebSocketClientDriver.py | 0 Python/AnP/Drivers/WebSocketServerDriver.py | 3 +- Python/AnP/Drivers/XMLScrapDriver.py | 103 +++++---- .../Abstracts/FilesAbstractInterface.py | 5 +- .../Interfaces/Application/AnPInterface.py | 2 + .../Managers/DatabasesManagerInterface.py | 31 +++ .../Managers/DispatchesManagerInterface.py | 10 +- Python/AnP/Managers/ControllersManager.py | 11 +- Python/AnP/Managers/DatabasesManager.py | 79 +++++++ Python/AnP/Managers/DispatchesManager.py | 62 ++++-- Python/AnP/Managers/TerminalManager.py | 8 +- Python/AnP/Models/BrowserTabsModel.py | 86 ++++++++ Python/AnP/Models/DatabaseResponseModel.py | 29 +++ Python/AnP/Models/XMLScrapTabModel.py | 17 -- Python/AnP/Utils/Checks.py | 7 +- Python/base.py | 4 +- README.md | 55 ++++- 35 files changed, 911 insertions(+), 296 deletions(-) create mode 100644 Python/AnP/Abstracts/DatabaseAbstract.py create mode 100644 Python/AnP/Drivers/PostgreSQLDriver.py create mode 100644 Python/AnP/Drivers/WebSocketClientDriver.py create mode 100644 Python/AnP/Interfaces/Managers/DatabasesManagerInterface.py create mode 100644 Python/AnP/Managers/DatabasesManager.py create mode 100644 Python/AnP/Models/BrowserTabsModel.py create mode 100644 Python/AnP/Models/DatabaseResponseModel.py delete mode 100644 Python/AnP/Models/XMLScrapTabModel.py diff --git a/JSON/I18N/AnP.i18n.espanol.json b/JSON/I18N/AnP.i18n.espanol.json index a4fb085..ea4de65 100644 --- a/JSON/I18N/AnP.i18n.espanol.json +++ b/JSON/I18N/AnP.i18n.espanol.json @@ -69,6 +69,7 @@ "anp_browser_action_not_done" : "La acción '{i}' de tipo '{action}' no ha sido ejecutada correctamente en el navegador '{key}'.", "anp_browser_action_not_supported" : "La acción '{i}' de tipo '{action}' no es compatible con el navegador '{key}'.", "anp_browser_action_not_action" : "El elemento '{i}' no es una acción válida en el navegador '{key}'.", + "anp_browser_abstract_do_action" : "Ejecutando la acción '{i}' de tipo '{action}' en el navegador '{key}'.", "AnP_BrowserAbstract_end" : null, "AnP_DirectoryCloneModel_start" : null, diff --git a/Python/AnP/Abstracts/AIInterpretersAbstract.py b/Python/AnP/Abstracts/AIInterpretersAbstract.py index 611c615..e3a8951 100644 --- a/Python/AnP/Abstracts/AIInterpretersAbstract.py +++ b/Python/AnP/Abstracts/AIInterpretersAbstract.py @@ -2,12 +2,13 @@ # -*- coding: utf-8 -*- from typing import Any, Self, Sequence, Optional, Callable -from abc import ABC, abstractmethod +from abc import abstractmethod from AnP.Interfaces.Application.AnPInterface import AnPInterface +from AnP.Abstracts.ModelAbstract import ModelAbstract from AnP.Models.AIResponseModel import AIResponseModel from AnP.Utils.Checks import Check -class AIInterpretersAbstract(ABC): +class AIInterpretersAbstract(ModelAbstract): def __init__(self:Self, anp:AnPInterface, key:str, inputs:str|dict[str, Any|None]|Sequence[Any|None]) -> None: diff --git a/Python/AnP/Abstracts/BrowserAbstract.py b/Python/AnP/Abstracts/BrowserAbstract.py index eb9654e..994507c 100644 --- a/Python/AnP/Abstracts/BrowserAbstract.py +++ b/Python/AnP/Abstracts/BrowserAbstract.py @@ -11,17 +11,16 @@ from AnP.DSLs.ScrapDSL import ( ) from AnP.Utils.Common import Common from AnP.Abstracts.ModelAbstract import ModelAbstract +from AnP.Models.BrowserTabsModel import BrowserBoxModel T = TypeVar("T") class BrowserAbstract(ModelAbstract): - def __init__(self:Self, anp:AnPInterface, key:str, TabType:T, inputs:Optional[dict[str, Any|None]|Sequence[Any|None]] = None) -> None: + def __init__(self:Self, anp:AnPInterface, key:str, inputs:Optional[dict[str, Any|None]|Sequence[Any|None]] = None) -> None: self.anp:AnPInterface = anp self.key:str = key self.working:bool = True - self.tabs:dict[str, T] = {} - self.tab_selected:str = "" self.human_time:float|int|Sequence[float|int] = Common.get_value("human_time", inputs, 0.0) self.actions:dict[Action, Callable[[Action, dict[str, Any|None], Optional[Any]], None]] = { Execute : self.execute, @@ -35,9 +34,13 @@ class BrowserAbstract(ModelAbstract): ForEach : self.foreach, And : self._and, Or : self._or, - Not : self._not + Not : self._not, + If : self._if, + While : self._while, + DoWhile : self.do_while, + Download : self.download } - self.__is_working:dict[str, Callable[[], bool]] = {} + self.__print_actions:bool = Common.get_value("print_actions", inputs, True) def start(self:Self) -> None: self.working = True @@ -47,33 +50,23 @@ class BrowserAbstract(ModelAbstract): def remove(self:Self) -> None: self.stop() - self.tabs.clear() - self.tab_selected = "" - def is_working(self:Self, shared:Optional[dict[str, Any|None]] = None) -> bool: + def is_working(self:Self, box:BrowserBoxModel) -> bool: return self.working and self.anp.working() and ( - shared is None or - "__is_working_key__" not in shared or - shared["__is_working_key__"] not in self.__is_working or - self.__is_working[shared["__is_working_key__"]]() + box.is_working is None or box.is_working() ) def run(self:Self, actions:Sequence[Action], shared:dict[str, Any|None] = {}, - key:Optional[str] = None, is_working:Optional[Callable[[], bool]] = None ) -> dict[str, Any|None]: - - if key is not None and is_working is not None: - self.__is_working[key] = is_working - shared["__is_working_key__"] = key - self.do(Do(*actions), shared) + self.do(Do(*actions), shared, BrowserBoxModel(is_working)) return shared - def execute(self:Self, action:Execute, shared:dict[str, Any|None] = {}, last_item:Optional[Any] = None) -> None: + def execute(self:Self, action:Execute, shared:dict[str, Any|None], box:BrowserBoxModel) -> None: try: action.callback(shared) action.done = True @@ -81,30 +74,30 @@ class BrowserAbstract(ModelAbstract): action.exception = exception @abstractmethod - def open(self:Self, action:Open, shared:dict[str, Any|None] = {}, last_item:Optional[Any] = None) -> None:pass + def open(self:Self, action:Open, shared:dict[str, Any|None], box:BrowserBoxModel) -> None:pass - @abstractmethod - def close(self:Self, action:Close, shared:dict[str, Any|None] = {}, last_item:Optional[Any] = None) -> None:pass + def close(self:Self, action:Close, shared:dict[str, Any|None], box:BrowserBoxModel) -> None: + action.done = box.close(action.key) - @abstractmethod - def switch(self:Self, action:Switch, shared:dict[str, Any|None] = {}, last_item:Optional[Any] = None) -> None:pass + def switch(self:Self, action:Switch, shared:dict[str, Any|None], box:BrowserBoxModel) -> None: + action.done = box.switch(action.key) - def wait(self:Self, action:Wait, shared:dict[str, Any|None] = {}, last_item:Optional[Any] = None) -> None: + def wait(self:Self, action:Wait, shared:dict[str, Any|None], box:BrowserBoxModel) -> None: date:float = timestamp() time:int|float = ( action.time if action.is_fixed else - Common.random_number(*action.time[0]) if action.is_range else + Common.random_number(*action.time) if action.is_range else Common.random(*action.time)) - while self.is_working(shared): + while self.is_working(box): sleep(.1) if timestamp() - date >= time: break action.done = True - def do(self:Self, action:Do, shared:dict[str, Any|None] = {}, last_item:Optional[Any] = None) -> None: + def do(self:Self, action:Do, shared:dict[str, Any|None], box:BrowserBoxModel) -> None: subaction:Action i:int @@ -112,10 +105,16 @@ class BrowserAbstract(ModelAbstract): action.done = True for i, subaction in enumerate(action.actions): - if not self.is_working(shared): + if not self.is_working(box): action.done = False return + self.__print_actions and self.anp.print("info", "anp_browser_abstract_do_action", { + "key" : self.key, + "i" : i, + "action" : subaction.__class__.__name__ + }) + if isinstance(subaction, Action): subaction.done = False subaction.exception = None @@ -124,7 +123,7 @@ class BrowserAbstract(ModelAbstract): ok:bool - self.actions[subaction.__class__](subaction, shared, last_item) + self.actions[subaction.__class__](subaction, shared, box) ok = subaction.done and subaction.exception is None if not ok and not subaction.ignore_fail: @@ -144,7 +143,7 @@ class BrowserAbstract(ModelAbstract): break if subaction.wait: - self.wait(Wait(self.human_time, True)) + self.wait(Wait(self.human_time, True), shared, box) ok and subaction.print_ok and self.anp.print("info", "anp_browser_action_done", { "key" : self.key, @@ -182,10 +181,10 @@ class BrowserAbstract(ModelAbstract): @abstractmethod - def go(self:Self, action:Go, shared:dict[str, Any|None] = {}, last_item:Optional[Any] = None) -> None:pass + def go(self:Self, action:Go, shared:dict[str, Any|None], box:BrowserBoxModel) -> None:pass @abstractmethod - def get_selector(self:Self, selector:Selector, from_item:Optional[Any] = None, force_multiple:bool = False) -> Any|list[Any]|None:pass + def get_selector(self:Self, selector:Selector, box:BrowserBoxModel, force_multiple:bool = False) -> Any|list[Any]|None:pass @staticmethod def get_value(action:Get, value:str) -> str: @@ -194,19 +193,20 @@ class BrowserAbstract(ModelAbstract): return action.pattern.sub(action.matches, value) @abstractmethod - def get(self:Self, action:Get, shared:dict[str, Any|None] = {}, last_item:Optional[Any] = None) -> None:pass + def get(self:Self, action:Get, shared:dict[str, Any|None], box:BrowserBoxModel) -> None:pass - def foreach(self:Self, action:ForEach, shared:dict[str, Any|None] = {}, last_item:Optional[Any] = None) -> None: + def foreach(self:Self, action:ForEach, shared:dict[str, Any|None], box:BrowserBoxModel) -> None: item:Any|None done:bool = True is_selector:bool = isinstance(action.items, Selector) + last:Any|None = box.get_last() for item in ( - self.get_selector(action.items, last_item, True) if is_selector else + self.get_selector(action.items, box, True) if is_selector else action.items): - if not self.is_working(shared): + if not self.is_working(box): done = False break @@ -220,91 +220,86 @@ class BrowserAbstract(ModelAbstract): subactions:Do = Do(*action.actions) - self.do(subactions, shared, item if is_selector else last_item) + box.set_last(item) + + self.do(subactions, shared, box) if not subactions.done or subactions.exception is not None: action.exception = subactions.exception done = False break + box.reset() + box.set_last(last) + action.done = done - def _and(self:Self, action:And, shared:dict[str, Any|None] = {}, last_item:Optional[Any] = None) -> bool: + def __condition(self:Self, + action:Condition|bool|Selector|Callable[[dict[str, Any|None]], bool], + shared:dict[str, Any|None], + box:BrowserBoxModel + ) -> bool: + + if isinstance(action, And): + self._and(action, shared, box) + return action.ok + elif isinstance(action, Or): + self._or(action, shared, box) + return action.ok + elif isinstance(action, Not): + self._not(action, shared, box) + return action.ok + elif isinstance(action, bool): + return action + elif isinstance(action, Selector): + return self.get_selector(action, box) + elif callable(action): + try: + return action(shared) + except Exception as exception: + action.exception = exception + else: + action.print_errors and self.anp.print("warning", "anp_browser_condition_not_supported", { + "key" : self.key + }) + return False + + def _and(self:Self, action:And, shared:dict[str, Any|None], box:BrowserBoxModel) -> bool: condition:Callable[[dict[str, Any|None]], bool]|Selector|Condition action.ok = True for condition in action.conditions: - if isinstance(condition, Condition): - self.do(condition, shared, last_item) - if not condition.ok: - action.ok = False - break - elif isinstance(condition, Selector): - if not self.get_selector(condition, last_item): - action.ok = False - break - elif callable(condition): - try: - if not condition(shared): - action.ok = False - break - except Exception as exception: - action.exception = exception - action.ok = False - break + if not self.__condition(condition, shared, box): + action.ok = False + break - def _or(self:Self, action:Or, shared:dict[str, Any|None] = {}, last_item:Optional[Any] = None) -> bool: + def _or(self:Self, action:Or, shared:dict[str, Any|None], box:BrowserBoxModel) -> bool: condition:Callable[[dict[str, Any|None]], bool]|Selector|Condition action.ok = False for condition in action.conditions: - if isinstance(condition, Condition): - self.do(condition, shared, last_item) - if condition.ok: - action.ok = True - break - elif isinstance(condition, Selector): - if self.get_selector(condition, last_item): - action.ok = True - break - elif callable(condition): - try: - if condition(shared): - action.ok = True - break - except Exception as exception: - action.exception = exception - action.ok = False - break + if self.__condition(condition, shared, box): + action.ok = True + break - def _not(self:Self, action:Not, shared:dict[str, Any|None] = {}, last_item:Optional[Any] = None) -> bool: - if isinstance(action.condition, Condition): - self.do(action.condition, shared, last_item) - action.ok = not action.condition.ok - elif isinstance(action.condition, Selector): - action.ok = not self.get_selector(action.condition, last_item) - elif callable(action.condition): - try: - action.ok = not action.condition(shared) - except Exception as exception: - action.exception = exception - action.ok = False + def _not(self:Self, action:Not, shared:dict[str, Any|None], box:BrowserBoxModel) -> bool: + return not self.__condition(action.condition, shared, box) - def _if(self:Self, action:If, shared:dict[str, Any|None] = {}, last_item:Optional[Any] = None) -> None: + def _if(self:Self, action:If, shared:dict[str, Any|None], box:BrowserBoxModel) -> None: if not isinstance(action.condition, Condition): action.condition = Condition((action.condition,)) - self.do(action.condition, shared, last_item) + self.do(action.condition, shared, box) if action.condition.done and action.condition.exception is None: subactions:Do = Do(*((action.if_actions if action.condition.ok else action.else_actions) or [])) - self.do(subactions, shared, last_item) + self.do(subactions, shared, box) action.done = subactions.done and subactions.exception is None action.exception = subactions.exception @@ -312,17 +307,17 @@ class BrowserAbstract(ModelAbstract): action.done = action.condition.done action.exception = action.condition.exception - def _while(self:Self, action:While, shared:dict[str, Any|None] = {}, last_item:Optional[Any] = None) -> None: + def _while(self:Self, action:While, shared:dict[str, Any|None], box:BrowserBoxModel) -> None: if not isinstance(action.condition, Condition): action.condition = Condition((action.condition,)) while True: - if not self.is_working(shared): + if not self.is_working(box): action.done = False break - self.do(action.condition, shared, last_item) + self.do(action.condition, shared, box) if not action.condition.done or action.condition.exception is not None: action.done = action.condition.done @@ -333,7 +328,7 @@ class BrowserAbstract(ModelAbstract): subactions:Do = Do(*action.actions) - self.do(subactions, shared, last_item) + self.do(subactions, shared, box) if not subactions.done or subactions.exception is not None: action.exception = subactions.exception action.done = False @@ -343,25 +338,26 @@ class BrowserAbstract(ModelAbstract): action.done = True break - def do_while(self:Self, action:DoWhile, shared:dict[str, Any|None] = {}, last_item:Optional[Any] = None) -> None: + def do_while(self:Self, action:DoWhile, shared:dict[str, Any|None], box:BrowserBoxModel) -> None: if not isinstance(action.condition, Condition): action.condition = Condition((action.condition,)) while True: - if not self.is_working(shared): + if not self.is_working(box): action.done = False break subactions:Do = Do(*action.actions) - self.do(subactions, shared, last_item) + self.do(subactions, shared, box) if not subactions.done or subactions.exception is not None: action.exception = subactions.exception action.done = False break - self.do(action.condition, shared, last_item) + subactions = Do(*action.actions) + self.do(subactions, shared, box) if not action.condition.done or action.condition.exception is not None: action.done = action.condition.done @@ -373,4 +369,4 @@ class BrowserAbstract(ModelAbstract): break @abstractmethod - def download(self:Self, action:Download, shared:dict[str, Any|None] = {}, last_item:Optional[Any] = None) -> None:pass \ No newline at end of file + def download(self:Self, action:Download, shared:dict[str, Any|None], box:BrowserBoxModel) -> None:pass \ No newline at end of file diff --git a/Python/AnP/Abstracts/DatabaseAbstract.py b/Python/AnP/Abstracts/DatabaseAbstract.py new file mode 100644 index 0000000..b326e11 --- /dev/null +++ b/Python/AnP/Abstracts/DatabaseAbstract.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +from typing import Self, Any, Sequence, Optional +from abc import abstractmethod +from time import time as timestamp +from threading import Lock, Thread +from AnP.Interfaces.Application.AnPInterface import AnPInterface +from AnP.Models.DatabaseResponseModel import DatabaseResponseModel +from AnP.Abstracts.ModelAbstract import ModelAbstract +from AnP.Utils.Common import Common + +class DatabaseAbstract(ModelAbstract): + + def __init__(self:Self, anp:AnPInterface, inputs:dict[str, Any|None]|Sequence[Any|None]) -> None: + + self.anp:AnPInterface = anp + self.__lock:Lock = Lock() + self.__busy:bool = False + self.__working:bool = True + self.__connection_timer:float|int = Common.get_value("connection_timer", inputs, 300.0) + self.__last_connection:Any = 0.0 + self.__thread:Thread = Thread(target = self.__connection_controller) + file:str + + self.__thread.start() + + for file in Common.get_texts(Common.get_value("execute", inputs, [])): + self.execute_file(file) + + def close(self:Self) -> None: + self.disconnect() + self.__working = False + + @abstractmethod + def is_disconnected(self:Self) -> bool:pass + + def __connection_controller(self:Self) -> None: + while self.__working and self.anp.working(): + if self.is_disconnected(): + if timestamp() - self.__last_connection > self.__connection_timer: + self.disconnect() + self.anp.wait(1.0) + + @abstractmethod + def connect(self:Self) -> bool:pass + + @abstractmethod + def disconnect(self:Self) -> None:pass + + def _ready(self:Self) -> None: + while self.anp.working(): + with self.__lock: + if self.__busy: + self.anp.wait(1.0) + else: + self.__last_connection = timestamp() + self.__busy = True + break + + @abstractmethod + def execute(self:Self, query:str, inputs:Optional[dict[str, Any|None]|Sequence[Any|None]] = None) -> DatabaseResponseModel:pass + + def execute_file(self:Self, path:str, inputs:Optional[dict[str, Any|None]] = None) -> DatabaseResponseModel: + return self.execute(self.anp.files.load(path), inputs) + + def _unbusy(self:Self) -> None: + with self.__lock: + self.__busy = False + self.__last_connection = timestamp() \ No newline at end of file diff --git a/Python/AnP/Abstracts/FilesAbstract.py b/Python/AnP/Abstracts/FilesAbstract.py index 780f713..f9a50f6 100644 --- a/Python/AnP/Abstracts/FilesAbstract.py +++ b/Python/AnP/Abstracts/FilesAbstract.py @@ -2,14 +2,15 @@ # -*- coding: utf-8 -*- from typing import Any, Optional, Self, Sequence -from abc import ABC, abstractmethod +from abc import abstractmethod from os.path import dirname as directory_name, abspath as absolute_path from AnP.Interfaces.Application.AnPInterface import AnPInterface +from AnP.Abstracts.ModelAbstract import ModelAbstract from AnP.Utils.Common import Common from AnP.Utils.Checks import Check from AnP.Utils.Patterns import RE -class FilesAbstract(ABC): +class FilesAbstract(ModelAbstract): def __init__(self:Self, anp:AnPInterface, inputs:Optional[dict[str, Any|None]|Sequence[Any|None]] = None) -> None: self.anp:AnPInterface = anp @@ -95,4 +96,7 @@ class FilesAbstract(ABC): return results @abstractmethod - def list_directory_items(self:Self, path:str) -> list[str]:pass \ No newline at end of file + def list_directory_items(self:Self, path:str) -> list[str]:pass + + @abstractmethod + def prepare_directory(self:Self, path:str) -> bool:pass \ No newline at end of file diff --git a/Python/AnP/Abstracts/HTTPServersAbstract.py b/Python/AnP/Abstracts/HTTPServersAbstract.py index 2a24e6c..b1b8770 100644 --- a/Python/AnP/Abstracts/HTTPServersAbstract.py +++ b/Python/AnP/Abstracts/HTTPServersAbstract.py @@ -1,14 +1,15 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -from abc import ABC, abstractmethod +from abc import abstractmethod from typing import Any, Self, Optional, Sequence from re import Match as REMath import datetime from AnP.Interfaces.Application.AnPInterface import AnPInterface +from AnP.Abstracts.ModelAbstract import ModelAbstract from AnP.Utils.Common import Common -class HTTPServersAbstract(ABC): +class HTTPServersAbstract(ModelAbstract): DEFAULT_PORT:int = 8000 DEFAULT_HOST:str = "" diff --git a/Python/AnP/Abstracts/ModelAbstract.py b/Python/AnP/Abstracts/ModelAbstract.py index 73ba56f..18151d2 100644 --- a/Python/AnP/Abstracts/ModelAbstract.py +++ b/Python/AnP/Abstracts/ModelAbstract.py @@ -1,4 +1,6 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -class ModelAbstract:pass \ No newline at end of file +from abc import ABC + +class ModelAbstract(ABC):pass \ No newline at end of file diff --git a/Python/AnP/Abstracts/WebSocketServersAbstract.py b/Python/AnP/Abstracts/WebSocketServersAbstract.py index 836f4a1..abc49d0 100644 --- a/Python/AnP/Abstracts/WebSocketServersAbstract.py +++ b/Python/AnP/Abstracts/WebSocketServersAbstract.py @@ -2,12 +2,13 @@ # -*- coding: utf-8 -*- from typing import Any, Self, Optional, Sequence -from abc import ABC, abstractmethod +from abc import abstractmethod from AnP.Interfaces.Application.AnPInterface import AnPInterface +from AnP.Abstracts.ModelAbstract import ModelAbstract from AnP.Application.Event import Event from AnP.Utils.Common import Common -class WebSocketServersAbstract(ABC): +class WebSocketServersAbstract(ModelAbstract): def __init__(self:Self, anp:AnPInterface, key:str, inputs:Optional[dict[str, Any|None]|Sequence[Any|None]] = None) -> None: self.anp:AnPInterface = anp diff --git a/Python/AnP/Application/AnP.py b/Python/AnP/Application/AnP.py index 248837a..e7ab11b 100644 --- a/Python/AnP/Application/AnP.py +++ b/Python/AnP/Application/AnP.py @@ -16,6 +16,7 @@ from AnP.Managers.ModelsManager import ModelsManager from AnP.Managers.UniqueKeysManager import UniqueKeysManager from AnP.Managers.QueusManager import QueuesManager from AnP.Managers.SessionsManager import SessionsManager +from AnP.Managers.DatabasesManager import DatabasesManager from AnP.Managers.ControllersManager import ControllersManager from AnP.Managers.DispatchesManager import DispatchesManager from AnP.Managers.IndexesManager import IndexesManager @@ -56,6 +57,7 @@ class AnP: self.unique_keys:UniqueKeysManager = UniqueKeysManager(self) self.queues:QueuesManager = QueuesManager(self) self.sessions:SessionsManager = SessionsManager(self) + self.databases:DatabasesManager = DatabasesManager(self) self.controllers:ControllersManager = ControllersManager(self) self.dispatches:DispatchesManager = DispatchesManager(self) self.indexes:IndexesManager = IndexesManager(self) @@ -77,6 +79,7 @@ class AnP: self.unique_keys.update() self.queues.update() self.sessions.update() + self.databases.update() self.controllers.update() self.dispatches.update() self.indexes.update() @@ -97,6 +100,7 @@ class AnP: self.unique_keys.reset() self.queues.reset() self.sessions.reset() + self.databases.reset() self.controllers.reset() self.dispatches.reset() self.indexes.reset() @@ -115,6 +119,7 @@ class AnP: self.http_servers.close() self.browsers.close() self.directories_clones.close() + self.databases.close() def working(self:Self) -> bool: return self.__working diff --git a/Python/AnP/DSLs/ScrapDSL.py b/Python/AnP/DSLs/ScrapDSL.py index aaa7aaf..6dd695b 100644 --- a/Python/AnP/DSLs/ScrapDSL.py +++ b/Python/AnP/DSLs/ScrapDSL.py @@ -12,6 +12,7 @@ class Action: self.print_ok:bool = False self.done:bool = False self.wait:bool = True + self.ignore_fail:bool = False class Execute(Action): def __init__(self:Self, callback:Callable[[dict[str, Any|None]], None]) -> None: diff --git a/Python/AnP/Drivers/FilesDriver.py b/Python/AnP/Drivers/FilesDriver.py index 3ffaf1d..935ad1a 100644 --- a/Python/AnP/Drivers/FilesDriver.py +++ b/Python/AnP/Drivers/FilesDriver.py @@ -11,11 +11,10 @@ from os import ( listdir as list_directory_items ) from shutil import rmtree as remove_directory -from AnP.Abstracts.FilesAbstract import FilesAbstract -from AnP.Abstracts.ModelAbstract import ModelAbstract +from AnP.Abstracts.FilesAbstract import FilesAbstract, abstractmethod from AnP.Utils.Checks import Check -class FilesDriver(FilesAbstract, ModelAbstract): +class FilesDriver(FilesAbstract): def get_absolute_path(self:Self, path:str) -> str|None: @@ -124,4 +123,9 @@ class FilesDriver(FilesAbstract, ModelAbstract): "path" : path, "only_names" : only_names }) - return items \ No newline at end of file + return items + + def prepare_directory(self:Self, path:str) -> bool: + if self.exists(path): + return True + return self.make_directory(path) \ No newline at end of file diff --git a/Python/AnP/Drivers/HTTPServerDriver.py b/Python/AnP/Drivers/HTTPServerDriver.py index 19c0bdb..0204a6c 100644 --- a/Python/AnP/Drivers/HTTPServerDriver.py +++ b/Python/AnP/Drivers/HTTPServerDriver.py @@ -6,11 +6,10 @@ from threading import Thread from http.server import BaseHTTPRequestHandler, HTTPServer from http.cookies import SimpleCookie from AnP.Interfaces.Application.AnPInterface import AnPInterface -from AnP.Abstracts.ModelAbstract import ModelAbstract from AnP.Models.RequestModel import RequestModel from AnP.Abstracts.HTTPServersAbstract import HTTPServersAbstract -class HTTPServerDriver(HTTPServersAbstract, ModelAbstract): +class HTTPServerDriver(HTTPServersAbstract): class HTTPRequestHandler(BaseHTTPRequestHandler): diff --git a/Python/AnP/Drivers/MySQLDriver.py b/Python/AnP/Drivers/MySQLDriver.py index 1f776fc..7569b6f 100644 --- a/Python/AnP/Drivers/MySQLDriver.py +++ b/Python/AnP/Drivers/MySQLDriver.py @@ -1,2 +1,17 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- \ No newline at end of file +# -*- coding: utf-8 -*- + +from typing import Self, Any, Sequence +from AnP.Abstracts.DatabaseAbstract import DatabaseAbstract +from AnP.Interfaces.Application.AnPInterface import AnPInterface +from AnP.Utils.Common import Common + +class MySQLDriver(DatabaseAbstract): + + def __init__(self:Self, anp:AnPInterface, inputs:dict[str, Any|None]|Sequence[Any|None]) -> None: + self.anp:AnPInterface = anp + self.__host:str = Common.get_value("host", inputs) + self.__port:int = Common.get_value("port", inputs) + self.__user:str = Common.get_value("user", inputs) + self.__password:str = Common.get_value("password", inputs) + self.__database:str = Common.get_value("database", inputs) \ No newline at end of file diff --git a/Python/AnP/Drivers/OllamaDriver.py b/Python/AnP/Drivers/OllamaDriver.py index 448b6a9..beb21e9 100644 --- a/Python/AnP/Drivers/OllamaDriver.py +++ b/Python/AnP/Drivers/OllamaDriver.py @@ -3,15 +3,13 @@ from typing import Any, Optional, Self, Sequence, Callable from requests import post as Post, Response -# from pydantic import BaseModel from AnP.Interfaces.Application.AnPInterface import AnPInterface from AnP.Abstracts.AIInterpretersAbstract import AIInterpretersAbstract -from AnP.Abstracts.ModelAbstract import ModelAbstract from AnP.Models.AIResponseModel import AIResponseModel from AnP.Utils.Checks import Check from AnP.Utils.Common import Common -class OllamaDriver(AIInterpretersAbstract, ModelAbstract): +class OllamaDriver(AIInterpretersAbstract): def __init__(self:Self, anp:AnPInterface, diff --git a/Python/AnP/Drivers/PostgreSQLDriver.py b/Python/AnP/Drivers/PostgreSQLDriver.py new file mode 100644 index 0000000..38ca8b8 --- /dev/null +++ b/Python/AnP/Drivers/PostgreSQLDriver.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +from typing import Self, Any, Sequence +from AnP.Abstracts.DatabaseAbstract import DatabaseAbstract +from AnP.Interfaces.Application.AnPInterface import AnPInterface +from AnP.Utils.Common import Common + +class PostgreSQLDriver(DatabaseAbstract): + + def __init__(self:Self, anp:AnPInterface, inputs:dict[str, Any|None]|Sequence[Any|None]) -> None: + self.anp:AnPInterface = anp + self.__host:str = Common.get_value("host", inputs) + self.__port:int = Common.get_value("port", inputs) + self.__user:str = Common.get_value("user", inputs) + self.__password:str = Common.get_value("password", inputs) + self.__database:str = Common.get_value("database", inputs) \ No newline at end of file diff --git a/Python/AnP/Drivers/SQLServerDriver.py b/Python/AnP/Drivers/SQLServerDriver.py index 1f776fc..21e60d9 100644 --- a/Python/AnP/Drivers/SQLServerDriver.py +++ b/Python/AnP/Drivers/SQLServerDriver.py @@ -1,2 +1,17 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- \ No newline at end of file +# -*- coding: utf-8 -*- + +from typing import Self, Any, Sequence +from AnP.Abstracts.DatabaseAbstract import DatabaseAbstract +from AnP.Interfaces.Application.AnPInterface import AnPInterface +from AnP.Utils.Common import Common + +class SQLServerDriver(DatabaseAbstract): + + def __init__(self:Self, anp:AnPInterface, inputs:dict[str, Any|None]|Sequence[Any|None]) -> None: + self.anp:AnPInterface = anp + self.__host:str = Common.get_value("host", inputs) + self.__port:int = Common.get_value("port", inputs) + self.__user:str = Common.get_value("user", inputs) + self.__password:str = Common.get_value("password", inputs) + self.__database:str = Common.get_value("database", inputs) \ No newline at end of file diff --git a/Python/AnP/Drivers/SQLiteDriver.py b/Python/AnP/Drivers/SQLiteDriver.py index 1f776fc..e34a22b 100644 --- a/Python/AnP/Drivers/SQLiteDriver.py +++ b/Python/AnP/Drivers/SQLiteDriver.py @@ -1,2 +1,166 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- \ No newline at end of file +# -*- coding: utf-8 -*- + +from typing import Self, Any, Sequence, Optional +from sqlite3 import Connection, connect as sqlite_connect, Cursor +from re import Match as REMatch +from AnP.Abstracts.DatabaseAbstract import DatabaseAbstract +from AnP.Interfaces.Application.AnPInterface import AnPInterface +from AnP.Models.DatabaseResponseModel import DatabaseResponseModel +from AnP.Utils.Common import Common +from AnP.Utils.Patterns import RE +from AnP.Utils.Checks import Check + +class SQLiteDriver(DatabaseAbstract): + + def __init__(self:Self, anp:AnPInterface, inputs:dict[str, Any|None]|Sequence[Any|None]) -> None: + self.__connection:Connection|None = None + self.__path:str = Common.get_value("path", inputs) + super().__init__(anp, inputs) + + def connect(self:Self) -> bool: + if self.__connection is None: + try: + + directory:str = self.anp.files.get_parent(self.__path) + + self.anp.files.prepare_directory(directory) + self.__connection = sqlite_connect(self.__path, check_same_thread = False) + + except Exception as exception: + self.anp.exception(exception, "anp_sqlite_driver_connection_exception") + return False + return True + + def is_disconnected(self:Self) -> bool: + return self.__connection is None + + def disconnect(self:Self) -> bool: + if self.__connection is not None: + try: + self.__connection.close() + self.__connection = None + return True + except Exception as exception: + self.anp.exception(exception, "anp_sqlite_driver_disconnection_exception") + return False + + def __set_variables(self:Self, query:str, inputs:Optional[dict[str, Any|None]] = None) -> str: + + def callback(matches:REMatch) -> str: + + key:str = matches.group(1) + + if key in inputs: + + value:Any|None = inputs[key] + + return ( + "null" if value is None else + "'" + value.replace("'", "''") + "'" if Check.is_string(value) else + ("1" if value is True else "0") if Check.is_boolean(value) else + # "'" + value.strftime("%Y-%m-%d %H:%M:%S") + "'" if Check.is_date(value) else + "'" + value.isoformat(sep = " ") + "'" if Check.is_date(value) else + str(value)) + return matches.group(0) + + return RE.STRING_VARIABLES.sub(callback, query.strip()) + + def __process(self:Self, query:str, inputs:Optional[dict[str, Any|None]] = None) -> Any|None: + + sentences:list[str] = [] + variables:list[str] = [] + positions:dict[str, int|None] = { + ";" : -1, + "\"" : -1, + "'" : -1 + } + character:str + position:int|None + subquery:str = "" + + while True: + + lower:str|None = None + + for character, position in positions.items(): + if position is None: + continue + if position < 0: + positions[character] = query.find(character) + if positions[character] == -1: + positions[character] = None + continue + if lower is None or positions[character] < positions[lower]: + lower = character + + if lower is None: + subquery = (subquery + query).strip() + if len(subquery): + sentences.append(self.__set_variables(subquery, inputs)) + break + + subquery += query[:positions[lower] + 1] + query = query[positions[lower] + 1:] + + if lower == ";": + subquery = subquery.strip() + if len(subquery): + sentences.append(self.__set_variables(subquery, inputs)) + subquery = "" + + if not len(query): + break + + for character in positions.keys(): + if positions[character] is not None: + positions[character] -= positions[lower] + 1 + + return sentences, variables + + def execute(self:Self, query:str, inputs:Optional[dict[str, Any|None]] = None) -> DatabaseResponseModel: + + response:DatabaseResponseModel = DatabaseResponseModel() + + self._ready() + + if self.connect(): + + sentences:list[str] + variables:list[str] + sentence:str + i:int + cursor:Cursor = self.__connection.cursor() + + sentences, variables = self.__process(query, inputs) + + try: + + for i, sentence in enumerate(sentences): + + row:list[Any] + columns:list[str] + + cursor.execute(sentence) + columns = [ + column[0] for column in cursor.description + ] if cursor.description is not None else [] + self.__connection.commit() + + for row in cursor.fetchall(): + response.add(i, dict(zip(columns, row))) + + self.__connection.commit() + + except Exception as exception: + self.anp.exception(exception, "anp_sqlite_driver_execution_exception", { + "i" : i, + "sentence" : sentence + }) + self.__connection.rollback() + + response.set(variables) + + self._unbusy() + + return response \ No newline at end of file diff --git a/Python/AnP/Drivers/SeleniumDriver.py b/Python/AnP/Drivers/SeleniumDriver.py index 0531c58..6bb6dc5 100644 --- a/Python/AnP/Drivers/SeleniumDriver.py +++ b/Python/AnP/Drivers/SeleniumDriver.py @@ -13,11 +13,11 @@ from seleniumwire.request import Request from selenium.webdriver.common.action_chains import ActionChains from AnP.DSLs.ScrapDSL import ( Open, Close, Switch, Go, Selector, XPath, CSS, Name, LinkText, Class, ID, - Get, Download + Get, Download, Action ) from AnP.Interfaces.Application import AnPInterface from AnP.Abstracts.BrowserAbstract import BrowserAbstract -from AnP.Models.SeleniumTabModel import SeleniumTabModel +from AnP.Models.BrowserTabsModel import BrowserBoxModel from AnP.Utils.Checks import Check WireWebDriver = Union[WebDriver, webdriver.Firefox] @@ -25,61 +25,104 @@ WireWebDriver = Union[WebDriver, webdriver.Firefox] class SeleniumDriver(BrowserAbstract): def __init__(self:Self, anp:AnPInterface, key:str, inputs:Optional[dict[str, Any|None]|Sequence[Any|None]] = None) -> None: - super().__init__(anp, key, SeleniumTabModel, inputs) - self.browser:WireWebDriver = None + super().__init__(anp, key, inputs) self.__cache_directory:str = self.anp.settings.get(("selenium_cache_directory", "cache_directory"), inputs, "/Data/Cache/Selenium") self.__download_timeout:int = self.anp.settings.get(("selenium_download_timeout", "download_timeout"), inputs, 60) def remove(self:Self) -> None: self.stop() - self.browser and self.browser.quit() + self.browser is None or self.browser.quit() self.tabs.clear() self.tab_selected = "" - def __load(self:Self, url:str|Callable[[dict[str, Any|None]], str], shared:dict[str, Any|None] = {}) -> None: + def __load(self:Self, + action:Action, + raw_url:str|Callable[[dict[str, Any|None]], str], + shared:dict[str, Any|None] + ) -> None: - if Check.is_function(url): - url = url(shared) + url:str = ( + shared[raw_url] if raw_url in shared else + raw_url if Check.is_string(raw_url) else + raw_url(shared) if Check.is_function(raw_url) else + None) - self.browser.get(shared[url] if url in shared else url) - - self.browser.set_script_timeout(10) - self.browser.execute_async_script(""" - if(document.readyState == "complete") - arguments[arguments.length - 1](200); - else - window.addEventListener("load", () => arguments[arguments.length - 1](200)); - """) + if url is not None: + try: - def open(self:Self, action:Open, shared:dict[str, Any|None] = {}, last_item:Optional[Any] = None) -> None: - if self.browser is None: - self.browser = webdriver.Firefox() + self.browser.get(url) + + self.browser.set_script_timeout(10) + self.browser.execute_async_script(""" + if(document.readyState == "complete") + arguments[arguments.length - 1](200); + else + window.addEventListener("load", () => arguments[arguments.length - 1](200)); + """) + action.done = True + + except Exception as exception: + action.done = False + action.exception = exception else: - self.browser.switch_to.new_window("tab") - self.tabs[action.key] = self.browser.current_window_handle - self.tab_selected = action.key - if action.url is not None: - self.__load(action.url, shared) + action.done = False - def close(self:Self, action:Close, shared:dict[str, Any|None] = {}, last_item:Optional[Any] = None) -> None: - self.browser.close() - del self.tabs[action.key] - self.tab_selected = "" - action.done = True + def __set_selected(self:Self, key:str, box:BrowserBoxModel, id:str) -> None: + box.variables[key] = id + box.variables["__selected__"] = key + + def __get_selected(self:Self, key:str, box:BrowserBoxModel) -> str|None: + if "__selected__" in box.variables and box.variables["__selected__"] in box.variables: + return box.variables[key] + return None + + def open(self:Self, action:Open, shared:dict[str, Any|None], box:BrowserBoxModel) -> None: + try: + if self.browser is None: + self.browser = webdriver.Firefox() + box.set_base(self.browser) + else: + self.browser.switch_to.new_window("tab") + box.set(action.key, self.browser) + box.variables[action.key] = self.browser.current_window_handle + box.variables["__selected__"] = action.key + self.__load(action, action.url, shared, box) + except Exception as exception: + action.done = False + action.exception = exception + + def close(self:Self, action:Close, shared:dict[str, Any|None], box:BrowserBoxModel) -> None: + try: + + current:str = box.get_last() + tab:str = box.get(action.key) + + self.browser.switch_to.window(tab) + self.browser.close() + box.close(tab) + self.browser.switch_to.window(current) - def switch(self:Self, action:Switch, shared:dict[str, Any|None] = {}, last_item:Optional[Any] = None) -> None: - if action.key in self.tabs: - self.browser.switch_to.window(self.tabs[action.key]) - self.tab_selected = action.key action.done = True - def go(self:Self, action:Go, shared:dict[str, Any|None] = {}, last_item:Optional[Any] = None) -> None: - self.__load(action.url, shared) - action.done = True + except Exception as exception: + action.done = False + action.exception = exception + + def switch(self:Self, action:Switch, shared:dict[str, Any|None], box:BrowserBoxModel) -> None: + + tab:str = box.get(action.key) + + if tab is not None: + self.browser.switch_to.window(tab) + self.switch(tab) + action.done = True + + def go(self:Self, action:Go, shared:dict[str, Any|None], box:BrowserBoxModel) -> None: + self.__load(action, action.url, shared) def get_selector(self:Self, selector:Selector, - from_item:Optional[WebDriver|WebElement] = None, + box:BrowserBoxModel, force_multiple:bool = False ) -> list[WebElement]|WebElement|None: if self.tab_selected in self.tabs: @@ -92,17 +135,16 @@ class SeleniumDriver(BrowserAbstract): By.PARTIAL_LINK_TEXT if isinstance(selector, LinkText) else By.NAME if isinstance(selector, Name) else By.CSS_SELECTOR) - - if from_item is None: - from_item = self.browser + from_item:WebElement|WebDriver = box.get_base() if force_multiple or selector.multiple: return from_item.find_elements(mode, selector.selector) return from_item.find_element(mode, selector.selector) return [] if force_multiple or selector.multiple else None - def get(self:Self, action:Get, shared:dict[str, Any|None] = {}, last_item:Optional[Any] = None) -> None: + def get(self:Self, action:Get, shared:dict[str, Any|None], box:BrowserBoxModel) -> None: + last_item:WebElement|None = box.get_last() item:WebElement = last_item or self.browser if action.selector is None else self.get_selector(action.selector, last_item) multiple:bool = action.selector is not None and action.selector.multiple @@ -125,18 +167,9 @@ class SeleniumDriver(BrowserAbstract): "textContent" if action.attribute == "CDATA" else action.attribute)) - # if multiple: - # shared[action.key].extend( - # [self.get_value(action, element.text) for element in item] if action.attribute == "CDATA" else - # [self.get_value(action, element.get_attribute(action.attribute)) for element in item]) - # else: - # shared[action.key] = ( - # self.get_value(action, item.text) if action.attribute == "CDATA" else - # self.get_value(action, item.get_attribute(action.attribute))) - action.done = True - def __download_from_cache(self:Self, url:str, timeout:Optional[int|float] = None) -> bytes|None: + def __download_from_cache(self:Self, url:str, box:BrowserBoxModel, timeout:Optional[int|float] = None) -> bytes|None: request:Request date:float = timestamp() @@ -157,9 +190,9 @@ class SeleniumDriver(BrowserAbstract): ) return None - def __download_by_anchor_label(self:Self, url:str) -> bytes|None: + def __download_by_anchor_label(self:Self, url:str, box:BrowserBoxModel) -> bytes|None: - # l:int = len(self.browser.window_handles) + l:int = len(self.browser.window_handles) data:bytes|None = None self.browser.execute_script(""" @@ -174,19 +207,19 @@ class SeleniumDriver(BrowserAbstract): """, url) - data = self.__download_from_cache(url) + data = self.__download_from_cache(url, box) - # if l < len(self.browser.window_handles): - # self.browser.switch_to.window(self.browser.window_handles[-1]) - # self.browser.close() - # self.browser.switch_to.window(self.browser.window_handles[-1]) + if l < len(self.browser.window_handles): + self.browser.switch_to.window(self.browser.window_handles[-1]) + self.browser.close() + self.browser.switch_to.window(box.get_last()) return data - def __download_by_url(self:Self, url:str) -> bytes|None: + def __download_by_url(self:Self, url:str, box:BrowserBoxModel) -> bytes|None: return self.browser.get(url) - def download(self:Self, action:Download, shared:dict[str, Any|None] = {}, last_item:Optional[Any] = None) -> None: + def download(self:Self, action:Download, shared:dict[str, Any|None], box:BrowserBoxModel) -> None: url:str = action.url(shared) if Check.is_function(action.url) else action.url method:Callable[[str], bytes|None] @@ -200,7 +233,7 @@ class SeleniumDriver(BrowserAbstract): # self.__download_by_url ): - data:bytes|None = method(url) + data:bytes|None = method(url, box) if data is not None: if action.key: diff --git a/Python/AnP/Drivers/WebSocketClientDriver.py b/Python/AnP/Drivers/WebSocketClientDriver.py new file mode 100644 index 0000000..e69de29 diff --git a/Python/AnP/Drivers/WebSocketServerDriver.py b/Python/AnP/Drivers/WebSocketServerDriver.py index 9826191..3116536 100644 --- a/Python/AnP/Drivers/WebSocketServerDriver.py +++ b/Python/AnP/Drivers/WebSocketServerDriver.py @@ -7,11 +7,10 @@ from http.cookies import SimpleCookie from websockets.sync.server import serve as server_serve from websockets import Server as WebSocketServer, ClientConnection as WebSocketClient from AnP.Abstracts.WebSocketServersAbstract import WebSocketServersAbstract -from AnP.Abstracts.ModelAbstract import ModelAbstract from AnP.Interfaces.Application.AnPInterface import AnPInterface from AnP.Utils.Checks import Check -class WebSocketServerDriver(WebSocketServersAbstract, ModelAbstract): +class WebSocketServerDriver(WebSocketServersAbstract): def __init__(self:Self, anp:AnPInterface, key:str, inputs:Optional[dict[str, Any|None]|Sequence[Any|None]] = None) -> None: super().__init__(anp, key, inputs) diff --git a/Python/AnP/Drivers/XMLScrapDriver.py b/Python/AnP/Drivers/XMLScrapDriver.py index 6d00f94..3275030 100644 --- a/Python/AnP/Drivers/XMLScrapDriver.py +++ b/Python/AnP/Drivers/XMLScrapDriver.py @@ -1,76 +1,68 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -from typing import Self, Any, Optional, Sequence, Callable +from typing import Self, Any, Callable from requests import get, Response from parsel import Selector as ParseSelector, SelectorList as ParseSelectorList from AnP.DSLs.ScrapDSL import ( - Selector, CSS, XPath, Name, LinkText, Class, ID, Open, Close, Switch, - Go, Get, ForEach, Do, Condition, And, Or, Not, If, While, DoWhile, Download, - Action + Selector, CSS, XPath, Name, LinkText, Class, ID, Open, + Go, Get, Download, Action ) -from AnP.Interfaces.Application import AnPInterface from AnP.Abstracts.BrowserAbstract import BrowserAbstract -from AnP.Models.XMLScrapTabModel import XMLScrapTabModel +from AnP.Models.BrowserTabsModel import BrowserBoxModel +from AnP.Utils.Checks import Check class XMLScrapDriver(BrowserAbstract): - def __init__(self:Self, anp:AnPInterface, key:str, inputs:Optional[dict[str, Any|None]|Sequence[Any|None]] = None) -> None: - super().__init__(anp, key, XMLScrapTabModel, inputs) + def __load(self:Self, + action:Action, + key:str|None, + raw_url:str|Callable[[dict[str, Any|None]], str], + shared:dict[str, Any|None], + box:BrowserBoxModel + ) -> None: - def __load(self:Self, url:str) -> Exception|None: - try: + url:str = ( + shared[raw_url] if raw_url in shared else + raw_url if Check.is_string(raw_url) else + raw_url(shared) if Check.is_function(raw_url) else + None) - response:Response - - with get(url) as response: - self.tabs[self.tab_selected].set(response.text) - - except Exception as exception: - return exception - return None - - def open(self:Self, action:Open, shared:dict[str, Any|None] = {}, last_item:Optional[Any] = None) -> None: - - url:str = shared[action.url] if action.url in shared and isinstance(shared[action.url], str) else action.url - - self.tabs[action.key] = XMLScrapTabModel(action.key, url) - self.tab_selected = action.key if url is not None: - action.exception = self.__load(url) - action.done = action.exception is None + try: - def close(self:Self, action:Close, shared:dict[str, Any|None] = {}, last_item:Optional[Any] = None) -> None: - if action.key in self.tabs: - del self.tabs[action.key] - if len(self.tabs): - self.tab_selected = list(self.tabs.keys())[-1] - action.done = True + response:Response - def switch(self:Self, action:Switch, shared:dict[str, Any|None] = {}, last_item:Optional[Any] = None) -> None: - if action.key in self.tabs: - self.tab_selected = action.key - action.done = True + with get(url) as response: + box.set(key or box.get_selected(), ParseSelector(text = response.text, type = "html")) - def go(self:Self, action:Go, shared:dict[str, Any|None] = {}, last_item:Optional[Any] = None) -> None: + action.done = True + except Exception as exception: + action.done = False + action.exception = exception + else: + action.done = False - url:str = shared[action.url] if action.url in shared and isinstance(shared[action.url], str) else action.url - - action.exception = self.__load(url) - action.done = action.exception is None + def open(self:Self, action:Open, shared:dict[str, Any|None], box:BrowserBoxModel) -> None: + self.__load(action, action.key, action.url, shared, box) - def get_selector(self:Self, selector:str, from_item:Optional[Selector] = None, force_multiple:bool = False) -> Any|list[Any]|None: - if self.tab_selected in self.tabs: + def go(self:Self, action:Go, shared:dict[str, Any|None], box:BrowserBoxModel) -> None: + self.__load(action, action.key, action.url, shared, box) + + def get_selector(self:Self, selector:Selector, box:BrowserBoxModel, force_multiple:bool = False) -> Any|list[Any]|None: + + path:ParseSelector|None = box.get_last() + + if path is not None: - path:ParseSelector = self.tabs[self.tab_selected].selector items:ParseSelectorList = ( - path.css(selector) if isinstance(selector, CSS) else - path.xpath(selector) if isinstance(selector, XPath) else - path.css(f"[name='{selector}']") if isinstance(selector, Name) else - path.css(f"a:contains('{selector}')") if isinstance(selector, LinkText) else - path.css(f".{selector}") if isinstance(selector, Class) else - path.css(f"#{selector}") if isinstance(selector, ID) else - path.css(selector)) + path.css(selector.selector) if isinstance(selector, CSS) else + path.xpath(selector.selector) if isinstance(selector, XPath) else + path.css(f"[name='{selector.selector}']") if isinstance(selector, Name) else + path.css(f"a:contains('{selector.selector}')") if isinstance(selector, LinkText) else + path.css(f".{selector.selector}") if isinstance(selector, Class) else + path.css(f"#{selector.selector}") if isinstance(selector, ID) else + path.css(selector.selector)) return ( items if force_multiple or selector.multiple else @@ -78,9 +70,9 @@ class XMLScrapDriver(BrowserAbstract): None) return [] if force_multiple or selector.multiple else None - def get(self:Self, action:Get, shared:dict[str, Any|None] = {}, last_item:Optional[Any] = None) -> None: + def get(self:Self, action:Get, shared:dict[str, Any|None], box:BrowserBoxModel) -> None: - item:ParseSelector|ParseSelectorList|None = last_item if action.selector is None else self.get_selector(action.selector, last_item) + item:ParseSelector|ParseSelectorList|None = box.get_last() if action.selector is None else self.get_selector(action.selector, box) multiple:bool = action.selector is not None and action.selector.multiple attribute:str = "::text" if action.attribute == "CDATA" else f"::attr({action.attribute})" @@ -96,7 +88,7 @@ class XMLScrapDriver(BrowserAbstract): action.done = True - def download(self:Self, action:Download, shared:dict[str, Any|None] = {}, last_item:Optional[Any] = None) -> None: + def download(self:Self, action:Download, shared:dict[str, Any|None], box:BrowserBoxModel) -> None: if action.key or action.path: try: @@ -120,4 +112,5 @@ class XMLScrapDriver(BrowserAbstract): action.done = True except Exception as exception: + print(exception) action.exception = exception \ No newline at end of file diff --git a/Python/AnP/Interfaces/Abstracts/FilesAbstractInterface.py b/Python/AnP/Interfaces/Abstracts/FilesAbstractInterface.py index 0cb37a5..4691f9f 100644 --- a/Python/AnP/Interfaces/Abstracts/FilesAbstractInterface.py +++ b/Python/AnP/Interfaces/Abstracts/FilesAbstractInterface.py @@ -50,4 +50,7 @@ class FilesAbstractInterface(ABC, ModelAbstract): def load_json(self:Self, inputs:Any|None, only_dictionaries:bool = True) -> list[dict[str, Any|None]|Sequence[Any|None]]:pass @abstractmethod - def list_directory_items(self:Self, path:str) -> list[str]:pass \ No newline at end of file + def list_directory_items(self:Self, path:str) -> list[str]:pass + + @abstractmethod + def prepare_directory(self:Self, path:str) -> bool:pass \ No newline at end of file diff --git a/Python/AnP/Interfaces/Application/AnPInterface.py b/Python/AnP/Interfaces/Application/AnPInterface.py index fa75fcb..2adeede 100644 --- a/Python/AnP/Interfaces/Application/AnPInterface.py +++ b/Python/AnP/Interfaces/Application/AnPInterface.py @@ -12,6 +12,7 @@ from AnP.Interfaces.Managers.ModelsManagerInterface import ModelsManagerInterfac from AnP.Interfaces.Managers.UniqueKeysManagerInterface import UniqueKeysManagerInterface from AnP.Interfaces.Managers.QueusManagerInterface import QueusManagerInterface from AnP.Interfaces.Managers.SessionsManagerInterface import SessionsManagerInterface +from AnP.Interfaces.Managers.DatabasesManagerInterface import DatabasesManagerInterface from AnP.Interfaces.Managers.ControllersManagerInterface import ControllersManagerInterface from AnP.Interfaces.Managers.DispatchesManagerInterface import DispatchesManagerInterface from AnP.Interfaces.Managers.IndexesManagerInterface import IndexesManagerInterface @@ -35,6 +36,7 @@ class AnPInterface(ABC): self.unique_keys:UniqueKeysManagerInterface = None self.queues:QueusManagerInterface = None self.sessions:SessionsManagerInterface = None + self.databases:DatabasesManagerInterface = None self.controllers:ControllersManagerInterface = None self.dispatches:DispatchesManagerInterface = None self.indexes:IndexesManagerInterface = None diff --git a/Python/AnP/Interfaces/Managers/DatabasesManagerInterface.py b/Python/AnP/Interfaces/Managers/DatabasesManagerInterface.py new file mode 100644 index 0000000..59422f4 --- /dev/null +++ b/Python/AnP/Interfaces/Managers/DatabasesManagerInterface.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +from typing import Self, Any, Optional, TypeVar +from abc import ABC, abstractmethod +from AnP.Models.DatabaseResponseModel import DatabaseResponseModel + +ControllerType = TypeVar("ControllerType", bound = DatabaseResponseModel) + +class DatabasesManagerInterface(ABC): + + @abstractmethod + def update(self:Self) -> None:pass + + @abstractmethod + def reset(self:Self) -> None:pass + + @abstractmethod + def close(self:Self) -> None:pass + + @abstractmethod + def add(self:Self, inputs:Any|None, overwrite:bool = False) -> None:pass + + @abstractmethod + def execute(self:Self, key:str, query:str, inputs:Optional[dict[str, Any|None]] = None) -> DatabaseResponseModel:pass + + @abstractmethod + def execute_file(self:Self, key:str, path:str, inputs:Optional[dict[str, Any|None]] = None) -> DatabaseResponseModel:pass + + @abstractmethod + def get(self:Self, key:str, Type:type[ControllerType]) -> ControllerType|None:pass \ No newline at end of file diff --git a/Python/AnP/Interfaces/Managers/DispatchesManagerInterface.py b/Python/AnP/Interfaces/Managers/DispatchesManagerInterface.py index d6338df..0e45d67 100644 --- a/Python/AnP/Interfaces/Managers/DispatchesManagerInterface.py +++ b/Python/AnP/Interfaces/Managers/DispatchesManagerInterface.py @@ -1,9 +1,12 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -from typing import Any, Self +from typing import Any, Self, TypeVar +from AnP.Abstracts.DispatchAbstract import DispatchAbstract from abc import ABC, abstractmethod +DispatchType = TypeVar("DispatchType", bound = DispatchAbstract) + class DispatchesManagerInterface(ABC): @abstractmethod @@ -16,4 +19,7 @@ class DispatchesManagerInterface(ABC): def add(self:Self, inputs:Any|None, overwrite:bool = False) -> None:pass @abstractmethod - def execute(self:Self, key:str, method:str, *arguments:list[Any|None]) -> bool:pass \ No newline at end of file + def execute(self:Self, via:str, key:str, method:str, *arguments:list[Any|None]) -> bool:pass + + @abstractmethod + def get(self:Self, via:str, key:str, Type:type[DispatchType]) -> DispatchType|None:pass \ No newline at end of file diff --git a/Python/AnP/Managers/ControllersManager.py b/Python/AnP/Managers/ControllersManager.py index 3532507..11314bd 100644 --- a/Python/AnP/Managers/ControllersManager.py +++ b/Python/AnP/Managers/ControllersManager.py @@ -1,12 +1,14 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -from typing import Any, Self +from typing import Any, Self, TypeVar from AnP.Interfaces.Application.AnPInterface import AnPInterface from AnP.Abstracts.ControllerAbstract import ControllerAbstract from AnP.Models.RequestModel import RequestModel from AnP.Utils.Checks import Check +ControllerType = TypeVar("ControllerType", bound = ControllerAbstract) + class ControllersManager: def __init__(self:Self, anp:AnPInterface) -> None: @@ -64,4 +66,9 @@ class ControllersManager: if key in self.__controllers and hasattr(self.__controllers[key], method): getattr(self.__controllers[key], method)(request) return True - return False \ No newline at end of file + return False + + def get(self:Self, key:str, Type:type[ControllerType]) -> ControllerType|None: + if key in self.__controllers and isinstance(self.__controllers[key], Type): + return self.__controllers[key] + return None \ No newline at end of file diff --git a/Python/AnP/Managers/DatabasesManager.py b/Python/AnP/Managers/DatabasesManager.py new file mode 100644 index 0000000..b22aba6 --- /dev/null +++ b/Python/AnP/Managers/DatabasesManager.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +from typing import Self, Any, Optional +from inspect import isclass +from AnP.Abstracts.DatabaseAbstract import DatabaseAbstract +from AnP.Interfaces.Application.AnPInterface import AnPInterface +from AnP.Utils.Checks import Check +from AnP.Models.DatabaseResponseModel import DatabaseResponseModel + +class DatabasesManager: + + def __init__(self:Self, anp:AnPInterface) -> None: + self.anp:AnPInterface = anp + self.__databases:dict[str, DatabaseAbstract] = {} + + self.update() + + def update(self:Self) -> None: + + key:str + + for key in ( + "default_databases_files", "databases_files", + "default_databases", "databases" + ): + self.add(self.anp.settings.get(key), True) + + def reset(self:Self) -> None: + self.close() + self.update() + + def close(self:Self) -> None: + + database:DatabaseAbstract + + for database in self.__databases.values(): + database.close() + + def add(self:Self, inputs:Any|None, overwrite:bool = False) -> None: + + subinputs:dict[str, Any|None] + + for subinputs in self.anp.files.load_json(inputs, True): + + key:str + database:Any|None + + for key, database in subinputs.items(): + if Check.is_mark_key(key) and database is None: + continue + + DatabaseClass:type[DatabaseAbstract]|None + + if Check.is_string(database): + DatabaseClass = self.anp.models.get(DatabaseAbstract, database) + elif isclass(database) and issubclass(database, DatabaseAbstract): + DatabaseClass = database + elif Check.is_dictionary(database) and "type" in database and Check.is_string(database["type"]): + DatabaseClass = self.anp.models.get(DatabaseAbstract, database["type"]) + else: + continue + + if DatabaseClass and ( + overwrite or key not in self.__databases + ): + self.__databases[key] = DatabaseClass( + self.anp, database if Check.is_dictionary(database) else + {}) + + def execute(self:Self, key:str, query:str, inputs:Optional[dict[str, Any|None]] = None) -> DatabaseResponseModel: + if key in self.__databases: + return self.__databases[key].execute(query, inputs) + return DatabaseResponseModel() + + def execute_file(self:Self, key:str, path:str, inputs:Optional[dict[str, Any|None]] = None) -> DatabaseResponseModel: + if key in self.__databases: + return self.__databases[key].execute_file(path, inputs) + return DatabaseResponseModel() \ No newline at end of file diff --git a/Python/AnP/Managers/DispatchesManager.py b/Python/AnP/Managers/DispatchesManager.py index 58cb3e4..54dc28c 100644 --- a/Python/AnP/Managers/DispatchesManager.py +++ b/Python/AnP/Managers/DispatchesManager.py @@ -1,17 +1,19 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -from typing import Any, Self +from typing import Any, Self, TypeVar from AnP.Interfaces.Application.AnPInterface import AnPInterface from AnP.Abstracts.DispatchAbstract import DispatchAbstract from AnP.Utils.Checks import Check +DispatchType = TypeVar("DispatchType", bound = DispatchAbstract) + class DispatchesManager: def __init__(self:Self, anp:AnPInterface) -> None: self.anp:AnPInterface = anp - self.__dispatches:dict[str, DispatchAbstract] = {} + self.__dispatches:dict[str, dict[str, DispatchAbstract]] = {} self.update() @@ -35,32 +37,46 @@ class DispatchesManager: for subinputs in self.anp.files.load_json(inputs, True): key:str - dispatch:Any|None + subinputs:dict[str, Any|None] + # dispatch:Any|None - for key, dispatch in subinputs.items(): + for key, subinputs in subinputs.items(): if Check.is_mark_key(key) and dispatch is None: continue - DispatchClass:type[DispatchAbstract]|None + via:str + dispatch:Any|None - if Check.is_string(dispatch): - DispatchClass = self.anp.models.get(DispatchAbstract, dispatch) - elif issubclass(dispatch, DispatchAbstract): - DispatchClass = dispatch - elif Check.is_dictionary(dispatch) and "type" in dispatch and Check.is_string(dispatch["type"]): - DispatchClass = self.anp.models.get(DispatchAbstract, dispatch["type"]) - else: - continue + for via, dispatch in subinputs.items(): - if DispatchClass and ( - overwrite or key not in self.__dispatches - ): - self.__dispatches[key] = DispatchClass( - self.anp, dispatch if Check.is_dictionary(dispatch) else - None) + DispatchClass:type[DispatchAbstract]|None - def execute(self:Self, key:str, method:str, *arguments:list[Any|None]) -> bool: - if key in self.__dispatches and hasattr(self.__dispatches[key], method): - getattr(self.__dispatches[key], method)(*arguments) + if via not in self.__dispatches: + self.__dispatches[via] = {} + + if Check.is_string(dispatch): + DispatchClass = self.anp.models.get(DispatchAbstract, dispatch) + elif issubclass(dispatch, DispatchAbstract): + DispatchClass = dispatch + elif Check.is_dictionary(dispatch) and "type" in dispatch and Check.is_string(dispatch["type"]): + DispatchClass = self.anp.models.get(DispatchAbstract, dispatch["type"]) + else: + continue + + if DispatchClass and ( + overwrite or key not in self.__dispatches[via] + ): + self.__dispatches[via][key] = DispatchClass( + self.anp, dispatch if Check.is_dictionary(dispatch) else + None) + + def execute(self:Self, via:str, key:str, method:str, *arguments:list[Any|None]) -> bool: + if via in self.__dispatches and key in self.__dispatches[via] and hasattr(self.__dispatches[via][key], method): + getattr(self.__dispatches[via][key], method)(*arguments) return True - return False \ No newline at end of file + return False + + def get(self:Self, via:str, key:str, Type:type[DispatchType]) -> DispatchType|None: + if via in self.__dispatches and key in self.__dispatches[via] and isinstance(self.__dispatches[via][key], Type): + return self.__dispatches[via][key] + return None \ No newline at end of file diff --git a/Python/AnP/Managers/TerminalManager.py b/Python/AnP/Managers/TerminalManager.py index db0dab7..1b335ab 100644 --- a/Python/AnP/Managers/TerminalManager.py +++ b/Python/AnP/Managers/TerminalManager.py @@ -44,9 +44,11 @@ class TerminalManager: def __handler(self:Self) -> None: while self.anp.working(): + + order:str = input() + try: - order:str = input() command:str = order.split(" ")[0].lower() parameters:dict[str, Any|None] = {} arguments:list[Any|None] = [] @@ -64,7 +66,9 @@ class TerminalManager: }) except Exception as error: - self.anp.exception(error, "terminal_manager_exception") + self.anp.exception(error, "terminal_manager_exception", { + "command" : order + }) def add(self:Self, inputs:Any|None, overwrite:bool = False) -> None: if Check.is_array(inputs): diff --git a/Python/AnP/Models/BrowserTabsModel.py b/Python/AnP/Models/BrowserTabsModel.py new file mode 100644 index 0000000..34b3596 --- /dev/null +++ b/Python/AnP/Models/BrowserTabsModel.py @@ -0,0 +1,86 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +from typing import Self, Any, Optional, Callable, Sequence +from AnP.Utils.Checks import Check + +class BrowserTabsModel: + + def __init__(self:Self, item:Any) -> None: + self.item:Any = item + self.last:Any = item + + def set_last(self:Self, item:Any) -> None: + self.last = item + + def reset(self:Self) -> None: + self.last = self.item + +class BrowserBoxModel: + + def __init__(self:Self, is_working:Optional[Callable[[], bool]] = None) -> None: + self.tabs:dict[str, BrowserTabsModel] = {} + self.tab_selected:str = "" + self.variables:dict[str, Any|None] = {} + self.is_working:Callable[[], bool]|None = is_working + self._base:Any|None = None + + def set_base(self:Self, item:Any) -> None: + self._base = item + + def get_base(self:Self) -> Any|None: + return self._base + + def get_selected(self:Self) -> str: + return self.tab_selected + + def get_last(self:Self) -> Any|None: + if self.tab_selected in self.tabs: + return self.tabs[self.tab_selected].last + return None + + def set(self:Self, key:str, item:Any) -> None: + self.tabs[key] = BrowserTabsModel(item) + self.tab_selected = key + + def change(self:Self, key:str) -> bool: + if key in self.tabs: + self.tab_selected = key + return True + return False + + def set_last(self:Self, item:Any) -> bool: + if self.tab_selected in self.tabs: + self.tabs[self.tab_selected].set_last(item) + return True + return False + + def reset(self:Self) -> bool: + if self.tab_selected in self.tabs: + self.tabs[self.tab_selected].reset() + return True + return False + + def close(self:Self, keys:Optional[str|Sequence[str]] = None) -> bool: + + ok:bool = True + + for key in ( + keys if Check.is_array(keys) else + [keys] if Check.is_key(keys) else + list(self.tabs.keys()) if keys is None else + []): + if key in self.tabs: + del self.tabs[key] + if self.tab_selected == key: + self.tab_selected = next(iter(self.tabs), "") + else: + ok = False + + return ok + + def switch(self:Self, key:str) -> bool: + if key in self.tabs: + self.tab_selected = key + return True + return False \ No newline at end of file diff --git a/Python/AnP/Models/DatabaseResponseModel.py b/Python/AnP/Models/DatabaseResponseModel.py new file mode 100644 index 0000000..bcb1c76 --- /dev/null +++ b/Python/AnP/Models/DatabaseResponseModel.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +from typing import Self, Any + +class DatabaseResponseModel: + + def __init__(self:Self) -> None: + self.tables:list[list[dict[str, Any|None]]] = [] + self.variables:dict[str, Any|None] = {} + + def add(self:Self, table:int, row:dict[str, Any|None]) -> None: + while len(self.tables) <= table: + self.tables.append([]) + self.tables[table].append(row) + + def set(self:Self, variables:list[str]) -> None: + + variable:str + + for variable in variables: + + table:list[dict[str, Any|None]] = [] + + for table in reversed(self.tables): + if len(table) == 1: + if variable in table[0]: + self.variables[variable] = table[0][variable] + break \ No newline at end of file diff --git a/Python/AnP/Models/XMLScrapTabModel.py b/Python/AnP/Models/XMLScrapTabModel.py deleted file mode 100644 index e294ddc..0000000 --- a/Python/AnP/Models/XMLScrapTabModel.py +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -from typing import Self -from parsel import Selector - -class XMLScrapTabModel: - - def __init__(self:Self, key:str, url:str) -> None: - self.key:str = key - self.url:str = url - self.data:str = "" - self.selector:Selector|None = None - - def set(self:Self, data:str) -> None: - self.data = data - self.selector = Selector(text = data, type = "xml") \ No newline at end of file diff --git a/Python/AnP/Utils/Checks.py b/Python/AnP/Utils/Checks.py index 1ce6fe0..a14f4a9 100644 --- a/Python/AnP/Utils/Checks.py +++ b/Python/AnP/Utils/Checks.py @@ -5,6 +5,7 @@ from typing import Any, Self, Sequence from re import Pattern as REPattern from os.path import isfile as is_file from json import loads as json_decode +import datetime from AnP.Utils.Patterns import RE class Check: @@ -84,4 +85,8 @@ class Check: @staticmethod def is_file(path:str) -> bool: - return is_file(path) \ No newline at end of file + return is_file(path) + + @staticmethod + def is_date(item:Any|None) -> bool: + return isinstance(item, (datetime.date, datetime.datetime)) \ No newline at end of file diff --git a/Python/base.py b/Python/base.py index d293da7..9f57b7b 100644 --- a/Python/base.py +++ b/Python/base.py @@ -10,6 +10,7 @@ from AnP.Drivers.OllamaDriver import OllamaDriver from AnP.Drivers.FilesDriver import FilesDriver from AnP.Drivers.XMLScrapDriver import XMLScrapDriver from AnP.Drivers.SeleniumDriver import SeleniumDriver +from AnP.Drivers.SQLiteDriver import SQLiteDriver from AnP.Controllers.CloneController import CloneController from AnP.Controllers.AITranslateController import AITranslateController @@ -24,7 +25,8 @@ inputs:dict[str, dict[str, Any|None]] = { "OllamaDriver" : OllamaDriver, "FilesDriver" : FilesDriver, "XMLScrapDriver" : XMLScrapDriver, - "SeleniumDriver" : SeleniumDriver + "SeleniumDriver" : SeleniumDriver, + "SQLiteDriver" : SQLiteDriver } } diff --git a/README.md b/README.md index b55a6df..bbb92ed 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,19 @@ Framework. -Secrets Python file: +# Instalations + +```bash +#!/bin/bash + +apt update && apt -y upgrade && apt -y autoclean && apt -y autoremove +pyp3 install websockets selenium selenium-wire --break-system-packages + +``` + +# Settings + +## Root Paths ```py #!/usr/bin/env python3 @@ -18,7 +30,7 @@ secrets:dict[str, Any|None] = { ``` -Web Socket Server Settings: +## Web Socket Server ```json { @@ -32,7 +44,7 @@ Web Socket Server Settings: } ``` -HTTP Settings: +## HTTP Server ```json { @@ -47,7 +59,7 @@ HTTP Settings: } ``` -AI Interpreters Settings: +## AI Interpreters ```json { @@ -82,7 +94,7 @@ AI Interpreters Settings: } ``` -Directory Cloner Settings: +## Directory Cloner ```json { @@ -98,4 +110,35 @@ Directory Cloner Settings: } } } -``` \ No newline at end of file +``` + +## Browsers + +```json +{ + "default_browsers" : { + "selenium" : { + "type" : "Selenium", + "print_actions" : false + }, + "xml" : { + "type" : "XMLScrap", + "print_actions" : false + } + } +} +``` + +## Scrappers + +```json +{ + "default_scrappers_files" : { + "mt" : { + "type" : "MTScrapper", + "browser" : "selenium", + "url" : "https://www43.mejortorrent.eu/inicio", + "autorun" : true + } + } +} \ No newline at end of file