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 
    ```





### 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}')
    ```



