mirror of
https://github.com/spalencsar/bearhub.git
synced 2026-07-07 01:14:15 +02:00
gem selector sketch
This commit is contained in:
0
bauh/api/__init__.py
Normal file
0
bauh/api/__init__.py
Normal file
0
bauh/api/abstract/__init__.py
Normal file
0
bauh/api/abstract/__init__.py
Normal file
50
bauh/api/abstract/cache.py
Normal file
50
bauh/api/abstract/cache.py
Normal file
@@ -0,0 +1,50 @@
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Set
|
||||
|
||||
|
||||
class MemoryCache(ABC):
|
||||
"""
|
||||
Represents a memory cache.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def is_enabled(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def add(self, key: str, val: object):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def add_non_existing(self, key: str, val: object):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get(self, key: str):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def delete(self, key):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def keys(self) -> Set[str]:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def clean_expired(self):
|
||||
pass
|
||||
|
||||
|
||||
class MemoryCacheFactory(ABC):
|
||||
"""
|
||||
Instantiate new memory cache instances.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def new(self, expiration: int) -> MemoryCache:
|
||||
"""
|
||||
:param expiration: expiration time for the cache keys in seconds. Use -1 to disable this feature.
|
||||
:return:
|
||||
"""
|
||||
pass
|
||||
30
bauh/api/abstract/context.py
Normal file
30
bauh/api/abstract/context.py
Normal file
@@ -0,0 +1,30 @@
|
||||
import logging
|
||||
|
||||
from bauh.api.abstract.cache import MemoryCacheFactory
|
||||
from bauh.api.abstract.disk import DiskCacheLoaderFactory
|
||||
from bauh.api.http import HttpClient
|
||||
|
||||
|
||||
class ApplicationContext:
|
||||
|
||||
def __init__(self, disk_cache: bool, download_icons: bool, http_client: HttpClient, app_root_dir: str, i18n: dict,
|
||||
cache_factory: MemoryCacheFactory, disk_loader_factory: DiskCacheLoaderFactory,
|
||||
logger: logging.Logger):
|
||||
"""
|
||||
:param disk_cache: if package data should be cached to disk
|
||||
:param download_icons: if packages icons should be downloaded
|
||||
:param http_client: a shared instance of http client
|
||||
:param app_root_dir: GUI root dir
|
||||
:param i18n: the i18n dictionary keys
|
||||
:param cache_factory:
|
||||
:param disk_loader_factory:
|
||||
:param logger: a logger instance
|
||||
"""
|
||||
self.disk_cache = disk_cache
|
||||
self.download_icons = download_icons
|
||||
self.http_client = http_client
|
||||
self.app_root_dir = app_root_dir
|
||||
self.i18n = i18n
|
||||
self.cache_factory = cache_factory
|
||||
self.disk_loader_factory = disk_loader_factory
|
||||
self.logger = logger
|
||||
214
bauh/api/abstract/controller.py
Normal file
214
bauh/api/abstract/controller.py
Normal file
@@ -0,0 +1,214 @@
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
from abc import ABC, abstractmethod
|
||||
from pathlib import Path
|
||||
from typing import List, Set, Type
|
||||
|
||||
from bauh.api.abstract.context import ApplicationContext
|
||||
from bauh.api.abstract.disk import DiskCacheLoader
|
||||
from bauh.api.abstract.handler import ProcessWatcher
|
||||
from bauh.api.abstract.model import SoftwarePackage, PackageUpdate, PackageHistory, PackageSuggestion, PackageAction
|
||||
|
||||
|
||||
class SearchResult:
|
||||
|
||||
def __init__(self, installed: List[SoftwarePackage], new: List[SoftwarePackage], total: int):
|
||||
"""
|
||||
:param installed: already installed packages
|
||||
:param new: new packages found
|
||||
:param total: total number of applications actually found
|
||||
"""
|
||||
self.installed = installed
|
||||
self.new = new
|
||||
self.total = total
|
||||
|
||||
|
||||
class SoftwareManager(ABC):
|
||||
|
||||
"""
|
||||
Base controller class that will be called by the graphical interface to execute operations.
|
||||
"""
|
||||
|
||||
def __init__(self, context: ApplicationContext):
|
||||
"""
|
||||
:param context:
|
||||
"""
|
||||
self.context = context
|
||||
|
||||
@abstractmethod
|
||||
def search(self, words: str, disk_loader: DiskCacheLoader, limit: int) -> SearchResult:
|
||||
"""
|
||||
:param words: the words typed by the user
|
||||
:param disk_loader: a running disk loader thread that loads package data from the disk asynchronously
|
||||
:param limit: the max number of packages to be retrieved. <= 1 should retrieve everything
|
||||
:return:
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def read_installed(self, disk_loader: DiskCacheLoader, limit: int, only_apps: bool, pkg_types: Set[Type[SoftwarePackage]]) -> SearchResult:
|
||||
"""
|
||||
:param disk_loader: a running disk loader thread that loads application data from the disk asynchronously
|
||||
:param limit: the max number of packages to be retrieved. <= 1 should retrieve everything
|
||||
:param only_apps: if only application packages should be retrieved
|
||||
:param pkg_types: use 'None' to bring any or specify some
|
||||
:return:
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def downgrade(self, pkg: SoftwarePackage, root_password: str, handler: ProcessWatcher) -> bool:
|
||||
"""
|
||||
downgrades a package version
|
||||
:param pkg:
|
||||
:param root_password: the root user password (if required)
|
||||
:param handler: a subprocess handler
|
||||
:return:
|
||||
"""
|
||||
pass
|
||||
|
||||
def clean_cache_for(self, pkg: SoftwarePackage):
|
||||
"""
|
||||
Cleans cached package cached data. This default implementation only cleans the cached data from the heard disk
|
||||
:param pkg:
|
||||
:return:
|
||||
"""
|
||||
if pkg.supports_disk_cache() and os.path.exists(pkg.get_disk_cache_path()):
|
||||
shutil.rmtree(pkg.get_disk_cache_path())
|
||||
|
||||
@abstractmethod
|
||||
def update(self, pkg: SoftwarePackage, root_password: str, watcher: ProcessWatcher) -> bool:
|
||||
"""
|
||||
:param pkg:
|
||||
:param root_password: the root user password (if required)
|
||||
:param watcher:
|
||||
:return:
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def uninstall(self, pkg: SoftwarePackage, root_password: str, watcher: ProcessWatcher) -> bool:
|
||||
"""
|
||||
:param pkg:
|
||||
:param root_password: the root user password (if required)
|
||||
:param watcher:
|
||||
:return: if the uninstall succeeded
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_managed_types(self) -> Set[Type[SoftwarePackage]]:
|
||||
"""
|
||||
:return: the managed package class type
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_info(self, pkg: SoftwarePackage) -> dict:
|
||||
"""
|
||||
retrieve the package information
|
||||
:param pkg:
|
||||
:return: a dictionary with the attributes to be shown
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_history(self, pkg: SoftwarePackage) -> PackageHistory:
|
||||
"""
|
||||
:param pkg:
|
||||
:return:
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def install(self, pkg: SoftwarePackage, root_password: str, watcher: ProcessWatcher) -> bool:
|
||||
"""
|
||||
:param pkg:
|
||||
:param root_password: the root user password (if required)
|
||||
:param watcher:
|
||||
:return: if the installation succeeded
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def is_enabled(self) -> bool:
|
||||
"""
|
||||
:return: if the instance is available to perform actions
|
||||
"""
|
||||
pass
|
||||
|
||||
def cache_to_disk(self, pkg: SoftwarePackage, icon_bytes: bytes, only_icon: bool):
|
||||
"""
|
||||
Saves the package data to the hard disk.
|
||||
:param pkg:
|
||||
:param icon_bytes:
|
||||
:param only_icon: if only the icon should be saved
|
||||
:return:
|
||||
"""
|
||||
if pkg.supports_disk_cache():
|
||||
|
||||
if not only_icon:
|
||||
Path(pkg.get_disk_cache_path()).mkdir(parents=True, exist_ok=True)
|
||||
data = pkg.get_data_to_cache()
|
||||
|
||||
if data:
|
||||
with open(pkg.get_disk_data_path(), 'w+') as f:
|
||||
f.write(json.dumps(data))
|
||||
|
||||
if icon_bytes:
|
||||
Path(pkg.get_disk_cache_path()).mkdir(parents=True, exist_ok=True)
|
||||
|
||||
with open(pkg.get_disk_icon_path(), 'wb+') as f:
|
||||
f.write(icon_bytes)
|
||||
|
||||
@abstractmethod
|
||||
def requires_root(self, action: str, pkg: SoftwarePackage):
|
||||
"""
|
||||
if a given action requires root privileges to be executed. Current actions are: 'install', 'uninstall', 'downgrade', 'search', 'refresh'
|
||||
:param action:
|
||||
:param pkg:
|
||||
:return:
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def prepare(self):
|
||||
"""
|
||||
It prepares the manager to start working. It will be called by GUI. Do not call it within.
|
||||
:return:
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def list_updates(self) -> List[PackageUpdate]:
|
||||
"""
|
||||
:return: available package updates
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def list_warnings(self) -> List[str]:
|
||||
"""
|
||||
:return: a list of warnings to be shown to the user
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def list_suggestions(self, limit: int) -> List[PackageSuggestion]:
|
||||
"""
|
||||
:param limit: max suggestions to be returned. If limit < 0, it should not be considered
|
||||
:return: a list of package suggestions
|
||||
"""
|
||||
pass
|
||||
|
||||
def execute_custom_action(self, action: PackageAction, pkg: SoftwarePackage, root_password: str, watcher: ProcessWatcher) -> bool:
|
||||
"""
|
||||
At the moment the GUI implements this action. No need to implement it yourself.
|
||||
:param action:
|
||||
:param pkg:
|
||||
:param root_password:
|
||||
:param watcher:
|
||||
:return: if the action resulted in success
|
||||
"""
|
||||
pass
|
||||
42
bauh/api/abstract/disk.py
Normal file
42
bauh/api/abstract/disk.py
Normal file
@@ -0,0 +1,42 @@
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Type
|
||||
|
||||
from bauh.api.abstract.cache import MemoryCache
|
||||
from bauh.api.abstract.model import SoftwarePackage
|
||||
|
||||
|
||||
class DiskCacheLoader:
|
||||
"""
|
||||
Reads cached data from the disk and fills package instances.
|
||||
"""
|
||||
|
||||
def map(self, cache: MemoryCache, pkg_type: Type[SoftwarePackage]):
|
||||
"""
|
||||
maps a given cache instance for a given package type
|
||||
:param cache:
|
||||
:param pkg_type:
|
||||
:return:
|
||||
"""
|
||||
pass
|
||||
|
||||
def fill(self, pkg: SoftwarePackage):
|
||||
"""
|
||||
fill cached data from the disk of a given package instance
|
||||
If a cache mapping was previously done, then data retrieved will be cached to memory as well.
|
||||
:param pkg:
|
||||
:return:
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class DiskCacheLoaderFactory(ABC):
|
||||
|
||||
@abstractmethod
|
||||
def map(self, pkg_type: Type[SoftwarePackage], cache: MemoryCache):
|
||||
"""
|
||||
Associated a cache instance to instances of a given SoftwarePackage class
|
||||
:param pkg_type:
|
||||
:param cache:
|
||||
:return:
|
||||
"""
|
||||
pass
|
||||
67
bauh/api/abstract/handler.py
Normal file
67
bauh/api/abstract/handler.py
Normal file
@@ -0,0 +1,67 @@
|
||||
from typing import List
|
||||
|
||||
from bauh.api.abstract.view import MessageType, ViewComponent
|
||||
|
||||
|
||||
class ProcessWatcher:
|
||||
"""
|
||||
Represents an view component watching background processes. It's a bridge for ApplicationManager instances notifiy the view of processes progression and also
|
||||
request any user interaction without the need of knowing any GUI code.
|
||||
"""
|
||||
|
||||
def print(self, msg: str):
|
||||
"""
|
||||
prints a given message to the user. In the current GUI implementation, the message is printed on the terminal window ("Details")
|
||||
:param msg:
|
||||
:return:
|
||||
"""
|
||||
pass
|
||||
|
||||
def request_confirmation(self, title: str, body: str, components: List[ViewComponent] = None, confirmation_label: str = None, deny_label: str = None)-> bool:
|
||||
"""
|
||||
request a user confirmation. In the current GUI implementation, it shows a popup to the user.
|
||||
:param title: popup title
|
||||
:param body: popup body
|
||||
:param components: extra view components that will be rendered to the confirmation popup.
|
||||
:param confirmation_label: optional confirmation button label (default to 'yes')
|
||||
:param deny_label: optional deny button label (default to 'no')
|
||||
:return: if the request was confirmed by the user
|
||||
"""
|
||||
pass
|
||||
|
||||
def show_message(self, title: str, body: str, type_: MessageType = MessageType.INFO):
|
||||
"""
|
||||
shows a message to the user. In the current GUI implementation, it shows a popup.
|
||||
:param title:
|
||||
:param body:
|
||||
:param type_: determines the icon that will be displayed
|
||||
:return:
|
||||
"""
|
||||
pass
|
||||
|
||||
def change_status(self, msg: str):
|
||||
"""
|
||||
Changes the process status. In the current GUI implementation, the process status is displayed above the toolbar.
|
||||
:param msg: msg
|
||||
:return:
|
||||
"""
|
||||
pass
|
||||
|
||||
def change_substatus(self, msg: str):
|
||||
"""
|
||||
Changes the process substatus. In the current GUI implementation, the process substatus is displayed above the progress bar.
|
||||
:param msg:
|
||||
:return:
|
||||
"""
|
||||
|
||||
def change_progress(self, val: int):
|
||||
"""
|
||||
Changes the process progress. In the current GUI implementation, the progress is displayed as a bottom bar.
|
||||
:param val: a val between 0 and 100
|
||||
:return:
|
||||
"""
|
||||
|
||||
def should_stop(self) -> bool:
|
||||
"""
|
||||
:return: if the use requested to stop the process.
|
||||
"""
|
||||
222
bauh/api/abstract/model.py
Normal file
222
bauh/api/abstract/model.py
Normal file
@@ -0,0 +1,222 @@
|
||||
from abc import ABC, abstractmethod
|
||||
from enum import Enum
|
||||
from typing import List
|
||||
|
||||
from bauh.api.constants import CACHE_PATH
|
||||
|
||||
|
||||
class PackageAction:
|
||||
|
||||
def __init__(self, i18_label_key: str, i18n_status_key: str, icon_path: str, manager_method: str, requires_root: bool):
|
||||
"""
|
||||
:param i18_label_key: the i18n key that will be used to display the action name
|
||||
:param i18n_status_key: the i18n key that will be used to display the action name being executed
|
||||
:param icon_path: the action icon path. Use None for no icon
|
||||
:param manager_method: the SoftwareManager method name that should be called. The method must has the following parameters: (pkg: SoftwarePackage, root_password: str, watcher: ProcessWatcher)
|
||||
:param requires_root:
|
||||
"""
|
||||
self.id = id
|
||||
self.i18_label_key = i18_label_key
|
||||
self.i18n_status_key = i18n_status_key
|
||||
self.icon_path = icon_path
|
||||
self.manager_method = manager_method
|
||||
self.requires_root = requires_root
|
||||
|
||||
|
||||
class PackageStatus(Enum):
|
||||
READY = 1 # when all package data is already filled
|
||||
LOADING_DATA = 2 # when some package data is already being retrieved asynchronously
|
||||
|
||||
|
||||
class SoftwarePackage(ABC):
|
||||
|
||||
def __init__(self, id: str = None, version: str = None, name: str = None, description: str = None, latest_version: str = None,
|
||||
icon_url: str = None, status: PackageStatus = PackageStatus.READY, installed: bool = False, update: bool = False,
|
||||
size: int = None, categories: List[str] = None):
|
||||
"""
|
||||
:param id:
|
||||
:param version:
|
||||
:param name:
|
||||
:param description:
|
||||
:param latest_version:
|
||||
:param icon_url: the path to the package icon. It can be an URL or PATH
|
||||
:param status:
|
||||
:param installed:
|
||||
:param update: if there is an update for package
|
||||
:param size: package size in BYTES
|
||||
:param categories: package categories. i.e: video editor, web browser, etc
|
||||
"""
|
||||
self.id = id
|
||||
self.name = name
|
||||
self.description = description
|
||||
self.status = status
|
||||
self.version = version
|
||||
self.latest_version = latest_version
|
||||
self.icon_url = icon_url
|
||||
self.installed = installed
|
||||
self.update = update
|
||||
self.size = size
|
||||
self.categories = categories
|
||||
|
||||
@abstractmethod
|
||||
def has_history(self):
|
||||
"""
|
||||
:return: if the application has a commit history that can be shown to the user
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def has_info(self):
|
||||
"""
|
||||
:return: if the application has additional information that can be shown to the user
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def can_be_downgraded(self):
|
||||
pass
|
||||
|
||||
def can_be_uninstalled(self):
|
||||
return self.installed
|
||||
|
||||
def can_be_installed(self):
|
||||
return not self.installed
|
||||
|
||||
@abstractmethod
|
||||
def get_type(self):
|
||||
"""
|
||||
:return: a string that represents the application type
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_default_icon_path(self):
|
||||
"""
|
||||
:return: the path of a default icon when the application icon could not be retrieved (or will not be retrieved)
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_type_icon_path(self):
|
||||
"""
|
||||
:return: the path of the application type icon
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def is_application(self):
|
||||
"""
|
||||
:return: if the package is an application
|
||||
"""
|
||||
pass
|
||||
|
||||
def supports_disk_cache(self):
|
||||
"""
|
||||
:return: if some application data and icon should be cached to the user disk
|
||||
"""
|
||||
return self.installed and self.is_application()
|
||||
|
||||
def get_disk_cache_path(self):
|
||||
"""
|
||||
:return: base cache path for the specific app type
|
||||
"""
|
||||
return CACHE_PATH + '/' + self.get_type()
|
||||
|
||||
def get_disk_icon_path(self):
|
||||
return '{}/icon.png'.format(self.get_disk_cache_path())
|
||||
|
||||
def get_disk_data_path(self):
|
||||
return '{}/data.json'.format(self.get_disk_cache_path())
|
||||
|
||||
@abstractmethod
|
||||
def get_data_to_cache(self) -> dict:
|
||||
"""
|
||||
:return: the application data that should be cached in disk / memory for quick access
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def fill_cached_data(self, data: dict):
|
||||
"""
|
||||
sets cached data to the current instance
|
||||
:param data:
|
||||
:return:
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def can_be_run(self) -> str:
|
||||
"""
|
||||
:return: whether the app can be run via the GUI
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def get_command(self) -> str:
|
||||
"""
|
||||
:return: the command to run the application
|
||||
"""
|
||||
pass
|
||||
|
||||
def is_trustable(self) -> bool:
|
||||
"""
|
||||
:return: if the package is distributed by a trustable source
|
||||
"""
|
||||
return False
|
||||
|
||||
@abstractmethod
|
||||
def get_publisher(self) -> str:
|
||||
"""
|
||||
:return: the package publisher / maintainer
|
||||
"""
|
||||
pass
|
||||
|
||||
def get_custom_supported_actions(self) -> List[PackageAction]:
|
||||
"""
|
||||
:return: custom supported actions
|
||||
"""
|
||||
pass
|
||||
|
||||
def __str__(self):
|
||||
return '{} (id={}, name={})'.format(self.__class__.__name__, self.id, self.name)
|
||||
|
||||
|
||||
class PackageUpdate:
|
||||
|
||||
def __init__(self, pkg_id: str, version: str, pkg_type: str):
|
||||
"""
|
||||
:param pkg_id: an unique package identifier
|
||||
:param version: the new version
|
||||
:param pkg_type: the package type
|
||||
"""
|
||||
self.id = pkg_id
|
||||
self.version = version
|
||||
self.type = pkg_type
|
||||
|
||||
def __str__(self):
|
||||
return '{} (id={}, type={}, new_version={})'.format(self.__class__.__name__, self.id, self.type, self.type)
|
||||
|
||||
|
||||
class PackageHistory:
|
||||
|
||||
def __init__(self, pkg: SoftwarePackage, history: List[dict], pkg_status_idx: int):
|
||||
"""
|
||||
:param pkg
|
||||
:param history: a list with the package history.
|
||||
:param pkg_status_idx: 'history' index in which the application is current found
|
||||
"""
|
||||
self.pkg = pkg
|
||||
self.history = history
|
||||
self.pkg_status_idx = pkg_status_idx
|
||||
|
||||
|
||||
class SuggestionPriority(Enum):
|
||||
LOW = 0
|
||||
MEDIUM = 1
|
||||
HIGH = 2
|
||||
TOP = 3
|
||||
|
||||
|
||||
class PackageSuggestion:
|
||||
|
||||
def __init__(self, package: SoftwarePackage, priority: SuggestionPriority):
|
||||
self.package = package
|
||||
self.priority = priority
|
||||
90
bauh/api/abstract/view.py
Normal file
90
bauh/api/abstract/view.py
Normal file
@@ -0,0 +1,90 @@
|
||||
from abc import ABC
|
||||
from enum import Enum
|
||||
from typing import List, Set
|
||||
|
||||
|
||||
class MessageType:
|
||||
INFO = 0
|
||||
WARNING = 1
|
||||
ERROR = 2
|
||||
|
||||
|
||||
class ViewComponent(ABC):
|
||||
"""
|
||||
Represents a GUI component
|
||||
"""
|
||||
def __init__(self, id_: str):
|
||||
self.id = id_
|
||||
|
||||
|
||||
class InputViewComponent(ViewComponent):
|
||||
"""
|
||||
Represents an component which needs a user interaction to provide its value
|
||||
"""
|
||||
|
||||
|
||||
class InputOption:
|
||||
"""
|
||||
Represents a select component option.
|
||||
"""
|
||||
|
||||
def __init__(self, label: str, value: str, tooltip: str = None, icon_path: str = None):
|
||||
"""
|
||||
:param label: the string that will be shown to the user
|
||||
:param value: the option value (not shown)
|
||||
:param tooltip: an optional tooltip
|
||||
:param icon_path: an optional icon path
|
||||
"""
|
||||
if not label:
|
||||
raise Exception("'label' must be a not blank string")
|
||||
|
||||
if not value:
|
||||
raise Exception("'value' must be a not blank string")
|
||||
|
||||
self.label = label
|
||||
self.value = value
|
||||
self.tooltip = tooltip
|
||||
self.icon_path = icon_path
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.label) + hash(self.value)
|
||||
|
||||
|
||||
class SelectViewType(Enum):
|
||||
RADIO = 0
|
||||
COMBO = 1
|
||||
|
||||
|
||||
class SingleSelectComponent(InputViewComponent):
|
||||
|
||||
def __init__(self, type_: SelectViewType, label: str, options: List[InputOption], default_option: InputOption = None, max_per_line: int = 1, id_: str = None):
|
||||
super(SingleSelectComponent, self).__init__(id_=id_)
|
||||
if options is None or len(options) < 2:
|
||||
raise Exception("'options' must be a list with at least 2 elements")
|
||||
|
||||
self.type = type_
|
||||
self.label = label
|
||||
self.options = options
|
||||
self.value = default_option
|
||||
self.max_per_line = max_per_line
|
||||
|
||||
|
||||
class MultipleSelectComponent(InputViewComponent):
|
||||
|
||||
def __init__(self, label: str, options: List[InputOption], default_options: Set[InputOption] = None, max_per_line: int = 1, id_: str = None):
|
||||
super(MultipleSelectComponent, self).__init__(id_=id_)
|
||||
|
||||
if not options:
|
||||
raise Exception("'options' cannot be None or empty")
|
||||
|
||||
self.options = options
|
||||
self.label = label
|
||||
self.values = default_options if default_options else set()
|
||||
self.max_per_line = max_per_line
|
||||
|
||||
|
||||
class TextComponent(ViewComponent):
|
||||
|
||||
def __init__(self, html: str, id_: str = None):
|
||||
super(TextComponent, self).__init__(id_=id_)
|
||||
self.value = html
|
||||
4
bauh/api/constants.py
Normal file
4
bauh/api/constants.py
Normal file
@@ -0,0 +1,4 @@
|
||||
from pathlib import Path
|
||||
|
||||
HOME_PATH = Path.home()
|
||||
CACHE_PATH = '{}/.cache/bauh'.format(HOME_PATH)
|
||||
3
bauh/api/exception.py
Normal file
3
bauh/api/exception.py
Normal file
@@ -0,0 +1,3 @@
|
||||
|
||||
class NoInternetException(Exception):
|
||||
pass
|
||||
40
bauh/api/http.py
Normal file
40
bauh/api/http.py
Normal file
@@ -0,0 +1,40 @@
|
||||
import logging
|
||||
import time
|
||||
import traceback
|
||||
|
||||
import requests
|
||||
|
||||
|
||||
class HttpClient:
|
||||
|
||||
def __init__(self, logger: logging.Logger, max_attempts: int = 2, timeout: int = 30, sleep: float = 0.5):
|
||||
self.max_attempts = max_attempts
|
||||
self.session = requests.Session()
|
||||
self.timeout = timeout
|
||||
self.sleep = sleep
|
||||
self.logger = logger
|
||||
|
||||
def get(self, url: str):
|
||||
cur_attempts = 1
|
||||
|
||||
while cur_attempts <= self.max_attempts:
|
||||
cur_attempts += 1
|
||||
|
||||
try:
|
||||
res = self.session.get(url, timeout=self.timeout)
|
||||
|
||||
if res.status_code == 200:
|
||||
return res
|
||||
|
||||
if self.sleep > 0:
|
||||
time.sleep(self.sleep)
|
||||
except:
|
||||
self.logger.error("Could not retrieve data from '{}'".format(url))
|
||||
traceback.print_exc()
|
||||
continue
|
||||
|
||||
self.logger.warning("Could not retrieve data from '{}'".format(url))
|
||||
|
||||
def get_json(self, url: str):
|
||||
res = self.get(url)
|
||||
return res.json() if res else None
|
||||
Reference in New Issue
Block a user