Metadata-Version: 2.4
Name: ruth-tools
Version: 2020.1.10
Summary: Python utility package
Author-email: Eyes Rutherford <ruth-tools@inbox.ru>
Requires-Python: >=3.7
Description-Content-Type: text/markdown
License-File: LICENSE.txt
Provides-Extra: startup-info
Provides-Extra: common
Provides-Extra: configurator
Requires-Dist: ruth-tools[common]; extra == "configurator"
Requires-Dist: PyYAML>=6.0.3; extra == "configurator"
Provides-Extra: logging
Requires-Dist: ruth-tools[common]; extra == "logging"
Requires-Dist: ruth-tools[configurator]; extra == "logging"
Provides-Extra: folders
Requires-Dist: ruth-tools[logging]; extra == "folders"
Provides-Extra: files
Requires-Dist: ruth-tools[logging]; extra == "files"
Requires-Dist: ruth-tools[folders]; extra == "files"
Requires-Dist: portalocker>=3.2.0; extra == "files"
Provides-Extra: tables
Requires-Dist: ruth-tools[logging]; extra == "tables"
Provides-Extra: databases-oracle
Requires-Dist: ruth-tools[configurator]; extra == "databases-oracle"
Requires-Dist: ruth-tools[logging]; extra == "databases-oracle"
Requires-Dist: ruth-tools[tables]; extra == "databases-oracle"
Requires-Dist: cx_Oracle>=8.3.0; extra == "databases-oracle"
Provides-Extra: databases
Requires-Dist: ruth-tools[databases-oracle]; extra == "databases"
Provides-Extra: versionizer
Requires-Dist: ruth-tools[logging]; extra == "versionizer"
Provides-Extra: emailing
Requires-Dist: ruth-tools[logging]; extra == "emailing"
Requires-Dist: email-validator>=2.3.0; extra == "emailing"
Provides-Extra: all
Dynamic: license-file

from xml.etree.ElementTree import indentfrom sys import prefix

# Tools
Python Utility Tools Library



### Startup Info
This module print in stdout general info at startup.
Should be added at the beginning of the main program or where you need to display this information:

- [ ] examples:
    ```python
    from tools.startup_info import startup_info
    startup_info()
    ```
  
    result:
    ```cmd
    --------------------------
    2020-11-12 13:14:15.678901
    User: DemoUser
    Host: DemoUser-PC
    OS Platform: Windows-10-10.0.19020-SP0
    Python Version: 3.11.2 (tags/v3.11.2:bffe2c1, Jan 08 2020, 10:12:11) [MSC v.1936 64 bit (AMD64)]
    Implementation: CPython
    -----------------------
    ```





### Common
This module contains tools for general use.


##### Attribute Manager
This class contains methods for managing class attributes taking in consideration real mangled name of each attribute.

- [ ] examples:
    ```python
    from tools.common import AttributeManager

    class SomeClass:
        __slots__ = ('__private_attr', '_protected_attr', 'public_attr')
        ...
        def as_static(self, attr_name: str):
            mangled_name = AttributeManager.get_name(obj=self, attr=attr_name)
            print(f'attribute_mangled_name: {mangled_name}')
            #
            found = AttributeManager.has_attribute(obj=self, attr=attr_name)
            print(f'found: {found}')
            #
            if not found:
                AttributeManager.set_attribute(obj=self, attr=attr_name, value=None)
            #
            value = AttributeManager.get_attribute(obj=self, attr=attr_name)
            print(f'value: {value}')
            pass
            
        def as_instance(self, attr_name: str):
            attr = AttributeManager(obj=self, attr=attr_name)
            #
            mangled_name = attr.name
            print(f'attribute_mangled_name: {mangled_name}')
            #
            found = attr.exists
            print(f'found: {found}')
            #
            if not found:
                attr.value = None
            #
            value = attr.value
            print(f'value: {value}')
            pass
        ...
    pass
    ```


##### Hide Big Content
This method tries to hide big content over a limit of characters.

- [ ] examples:
    ```python
    from tools.common import hide_big_content
  
    content = {
        'key': 'value',
        'l': [1, 'a', 'b'],
        's': 'aaa',
        't': (1, 2, 'a' * 5000)
    }
    print(f'content: {content}')
    processed = hide_big_content(value=content)
    print(f'processed: {processed}')
    ```


##### Mask password
This method tries to mask passwords from provided object.

- [ ] examples:
    ```python
    from tools.common import mask_password
  
    content = [
        1, 2, True,
        {
            'password': '12345',
            'pwd': 'x56',
            'pwd_lst': ['123', '345', '567']
        }
    ]
    print(f'content: {content}')
    processed = mask_password(value=content)
    print(f'processed: {processed}')
    ```


##### Get Location
Try to resolve file / method location. Can be used for template relative location

- [ ] examples:
    ```python
    from tools.common import get_location
  
    def func(): pass
  
    func_location = get_location(func=func)
    print(f'func_location: {func_location}')
  
    file_location = get_location(file=__file__)
    print(f'file_location: {file_location}')
    ```





### Configurator
This module try to get all application configurations from files and system environment and stores it as a global object to be used later.
Should be called at the beginning of main program.

- [ ] examples:

    load config:
    ```python
    from tools.configurator import Config, ConfigNode, ConfigLoader, ConfigLoaderIgnore
    Config.load(
        config=ConfigNode(
            config=ConfigLoader(
                path='../configs_folder',  # config files path
                paths=[  # or list of paths
                    './configs_folder_1',
                    './configs_folder_2'
                ],
                env_prefix='env_prefix',
                env_separator='::',
                ignore=ConfigLoaderIgnore(  # files / paths / env-vars to be ignored
                    file='test.py',  # file name
                    files=['test_1.py', 'test_2.py'],  # or list of file names
                    file_mask='test*',  # file name mask
                    file_masks=['templ-*', 'template-*']  # or list of file name masks
                ),
                extension='py',  # file extension
                extensions=['json', 'yaml'],  # or list of file extensions
                order=['env', 'json', 'yaml']  # loading config file order, Last Loaded value overrides Existing value for the same key
            ).config,
            read_only=True  # safe loading, forbid config changes after loading
        )
    )
    ```
    config parameter use:
    ```python
    from tools.configurator import Config
    
    print(f'param: {Config.param_group.param_subgroup.param_name}')
    ```





### Logging
This module injects into method a logger object and try to inspect and log input parameters and method result and/or error in a pretty way as a hierarchical tree of calls.
Can be used both for sync and async methods.

- [ ] examples:
    ```python
    from tools.logging import Logger, LogTypes, Console, File, LogContext, logging
  
    logger = Logger(
        type=[
            LogTypes.debug,
            LogTypes.info,
            LogTypes.warning,
            LogTypes.error
        ],
        outputs=(
            Console(
                enabled=True
            ),
            File(
                enabled=True,
                path='./logs',
                name='log.ext'
            )
        )
    )
  
    # as global logger ----
    from tools.configurator import Config, ConfigNode
    # config load
    Config.globals = ConfigNode()
    Config.globals.logger = logger
    
    # as logging context ----
    from tools.logging import LogContext
    log_ctx_token = LogContext.set(logger=logger)
    # some code
    # at the end
    LogContext.reset(token=log_ctx_token)    
    ```
  
    use for logs:
    ```python
    from tools.logging import logging
    
    @logging(prefix=__name__)
    def some_method():
        some_method.logger.debug(
            obj='some debug info',
            prefix='!!! > ',  # object prefix if needed
            date_: True,  # add date before log message
            indent: True,  # all logs from begin of start row or as hierarchy tree
            end_line: 1  # 0 - no end of line
        )
        # method logic here
        pass 
    ```





### Folders
This module was created to manage main folder operations.

- [ ] examples:
    ```python
    from tools.folders import Folder
    
    path = 'path/to/folder'

    if not Folder.exists(path=path):
        Folder.create(path='path/to/old-folder', recursive=True)
        
    Folder.remove(path='path/to/new-folder', recursive=True)
    ```





### Files
This module was created to perform main file operations.

- [ ] examples:
    ```python
    from tools.files import File
  
    path = 'path/to/file'
    name = 'file-name.ext'
  
    if not File.exists(path=path, name=name):
        File.write(path=path, name=name, content=None)
  
    demo = File.read(path=path, name=name)
    print(f'Demo file: {demo}')
  
    locked = File.lock(path=path, name=name)
    print('File is locked')
  
    try:
        File.write(path=path, name=name, content=b'new-content')
    except Exception as exc:
        print(f'Something got wrong: {exc}')
    
    File.unlock(file=locked)
    print('File is free to use')
  
    File.remove(path=path, name=name)
    ```





### Tables
This module was created to store data as a table, with data / column validation and filtering.
Cells can be accessed by index or by key.

- [ ] examples:
    ```python
    from tools.tables import Table
  
    table = Table(
        table={
            'columns': ['col-1', 'col-2'],
            'data': [
                # row-1
                {'col-1': 1, 'col-2': 2},
                # row-2
                {'col-1': True, 'col-2': 'str'}
            ]
        }
    )
    print(f'table: {table}')
  
    filtered_1 = table.filter(exclude=('col-1',))
    print(f'filtered_1: {filtered_1}')
    
    filtered_2 = table.filter(include=('col-2',))
    print(f'filtered_2: {filtered_2}')
    ```





### Databases
This module was created to easily access different databases.
Now it supports only Oracle connection, other DBs will be added in future versions.


##### Oracle

- [ ] examples:
    ```python
    from tools.databases.oracle import Oracle
    
    conn = Oracle(db='tns', username='user', password='pwd')
    
    # query
    query = conn.sql(
        stmt='''
            select :test
            from DUAL d
        ''',
        params={
            'test': 123
        }
    )
    print(f'query: {query}')

    dml = conn.sql(
        stmt='''
            delete
            from TABLE_NAME tn
            where tn.ID > :id
        ''',
        params={
            'id': 1234
        }
    )
    print(f'dml: {dml}')
  
    plsql = conn.plsql(
        stmt='''
            begin
                :val2 := :val1 + 5;
                :val4 := :val4 + :val2;
            end;
        ''',
        params={
            'val1': 10,
            'val4': 3
        },
        params_out={
            'val2': int,
            'val4': int
        }
    )
    print(f'plsql: {plsql}')
    ```





### Versionizer
This module was created to easily manage package versions and switch to newer version based on date and time.
It can be used to get current version of package and last existing version.

- [ ] examples:
    Below code should be added in your_awesome_module **__init__** file:

    ```python
    from tools.versionizer import VersionManager
    from datetime import datetime
  
    current = VersionManager(
        module=__name__,
        versions={
            'v1': datetime(2020, 1, 1),
            'v2': datetime(2023, 2, 4),
            'vX': datetime(2100, 1, 1)
        }
    )
    ```
    
    or:
   ```python
    from tools.versionizer import VersionManager
    from datetime import datetime
  
    __versions = VersionManager(
        module=__name__,
        versions={
            'v1': datetime(2020, 1, 1),
            'v2': datetime(2023, 2, 4),
            'vX': datetime(2100, 1, 1)
        }
    )
  
    current = __versions.current
    ```

    and **v1**, **v2**, **vX** sub-modules should be created and called:
    ```python
    from your_awesome_module import current
    from time import sleep
  
    while True:
        current.test_func()  # should return test_func from current version of module
        sleep(60 * 60)
    ```





### Emailer
This module was created to use in email address validation and sending emails with/without message content and attachments.

- [ ] examples:
    Email address validation:

    ```python
    from tools.emailer import EmailAddress
    
    # instance
    email = EmailAddress(email='some@email.com')
    print(f'Email address "{email.email}" is valid: {email.is_valid}')
    
    # static
    for email_address in ('first@email.com', 'second@email.com', 'third@email.com'):
        is_valid, details = EmailAddress.validate(email=email_address)
        print(f'Email address "{email_address}" is valid: {is_valid}')
    ```
  
    Email address sending:
-   ```python
    from tools.emailer import Email
    
    email = Email(
        server='email@server.com', username='usr', password='pwd',
        from_='from@email.com',
        to=['to-1@email.com', 'to-2@email.com'],
        cc=['cc-1@email.com', 'cc-2@email.com'],
        bcc=['bcc-1@email.com', 'bcc-2@email.com'],
        subject='subject',
        message='message',
        attachments=[
            ('att-1.txt', b'1st file content'),
            ('att-2.txt', b'2nd file content')
        ]
    )

    email.send()
    ```
