atckit


Nameatckit JSON
Version 2.0.1 PyPI version JSON
download
home_pageNone
SummaryAccidentallyTheCables Utility Kit
upload_time2025-07-11 02:19:51
maintainerNone
docs_urlNone
authorNone
requires_python>=3.11
licenseGPLv3
keywords
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # ATCKit

AccidentallyTheCable's Utility Kit

- [ATCKit](#atckit)
  - [About](#about)
    - [How does it work?](#how-does-it-work)
  - [Usage](#usage)
    - [FunctionSubscriber (atckit.subscriber)](#functionsubscriber-atckitsubscriber)
  - [Core Functions (atckit)](#core-functions-atckit)
    - [`create_object_logger`](#create_object_logger)
    - [`create_static_logger`](#create_static_logger)
    - [`deltatime_str`](#deltatime_str)
    - [`deep_sort`](#deep_sort)
  - [File Utils (atckit.files)](#file-utils-atckitfiles)
    - [`dump_sstr`](#dump_sstr)
    - [`load_sstr`](#load_sstr)
    - [`load_sfile`](#load_sfile)
    - [`scan_dir`](#scan_dir)
    - [`find_config_file`](#find_config_file)
    - [`add_config_search_path`](#add_config_search_path)
    - [`remove_config_search_path`](#remove_config_search_path)
    - [`add_config_search_file_ext`](#add_config_search_file_ext)
    - [`remove_config_search_file_ext`](#remove_config_search_file_ext)
  - [Signals (atckit.signals)](#signals-atckitsignals)
    - [`check_pid`](#check_pid)
    - [`register_pid`](#register_pid)
    - [`register_signals`](#register_signals)
  - [Service (atckit.service)](#service-atckitservice)
  - [Version (atckit.version)](#version-atckitversion)
    - [Version Search Strings](#version-search-strings)
      - [`version_locator`](#version_locator)
      - [`version_search_merge`](#version_search_merge)

## About

This is a small kit of classes, util functions, etc that I found myself rewriting or reusing frequently, and instead of copying everywhere, they are now here.

> **WARNING**: Version 2.0 is a breaking change from 1.x versions
>   2.0 Removes the static class and moves things around. Please check the docs below for where things are now

### How does it work?

Do the needfuls.... *do the needful dance*

Literally, import whatever you need to use..

## Usage

### FunctionSubscriber (atckit.subscriber)

A Class container for Function callback subscription via `+=` or `-=`. Functions can be retrieved in order of addition.

```
subscriber = FunctionSubscriber()

def a():
    print("I am a teapot")

def b():
    print("I am definitely totally not also a teapot, I swear")

subscriber += a
subscriber += b

for cb in subscriber.functions:
    cb()

>> I am a teapot
>> I am definitely totally not also a teapot, I swear
```

This class uses the `typing.Callable` type for function storage. You can extend the `FunctionSubscriber` class to define the
callback function parameters, etc.

```
class MySubscriber(FunctionSubscriber):
    """My Function Subscriber
    Callback: (bool) -> None
    """

    _functions:list[Callable[[bool],None]]

    def __iadd__(self,fn:Callable[[bool],None]) -> Self:
        """Inline Add. Subscribe Function
        @param method \c fn Method to Subscribe
        """
        return super().__iadd__(fn)

    def __isub__(self,fn:Callable[[bool],None]) -> Self:
        """Inline Subtract. Unsubscribe Function
        @param method \c fn Method to Unsubscribe
        """
        return super().__isub__(fn)
```

## Core Functions (atckit)

### `create_object_logger`

 Create `logging.Logger` instance for object specifically

### `create_static_logger`

 Create `logging.Logger` instance of a specified name

### `deltatime_str`

 Create `datetime.timedelta` from short formatted time string. Format: `0Y0M0w0d0h0m0s0ms`

### `deep_sort`
 
 Sort a Dictionary recursively, including through lists of dicts

## File Utils (atckit.files)

Classes and functions located in the `files` module

### `dump_sstr`
 
 Dump Structured Data (dict) to str of specified format. Accepts JSON, YAML, TOML

### `load_sstr`

 Load Structured Data from String. Accepts JSON, YAML, TOML

### `load_sfile`
 
 Load Structured Data from File, automatically determining data by file extension. Accepts JSON, YAML, TOML
### `scan_dir`
 
 Search a specified Path, and execute a callback function on discovered files.
   - Allows exclusion of Files/Dirs via regex pattern matching

### `find_config_file`
 
 Look for config file in 'well defined' paths. Searches for `<service>/<config>.[toml,json,yaml]` in `~/.local/` and `/etc/` (in that order)

### `add_config_search_path`
 
 Add Search Path for [`find_config_file`](#find_config_file)

### `remove_config_search_path`
 
 Remove Search Path for [`find_config_file`](#find_config_file)

### `add_config_search_file_ext`
 
 Add file extension for [`find_config_file`](#find_config_file)

### `remove_config_search_file_ext`
 
 Remove file extension for [`find_config_file`](#find_config_file)

## Signals (atckit.signals)

Signal Handling functions located in `signals`

### `check_pid`
 
 Check if a process ID exists (via kill 0)
### `register_pid`
 
 Register (Write) process ID in specified directory as `<service>.pid`
### `register_signals`
 
 Register Shutdown / Restart Handlers
   - Check for Shutdown via UtilFuncs.shutdown (bool)
   - Check for Restart via UtilFuncs.restart (bool)

## Service (atckit.service)

A Service / Daemon Class. Responds to signals properly, including HUP to restart threads

HUP does not restart main thread. So if the main configuration file needs to be re-read, the service needs to be stopped and started completely.

Entrypoint functions for services are defined under the `.services` [`FunctionSubscriber`](#FunctionSubscriber). These functions should be loopable, or be capable of starting again each time the function completes.

Create a class, which extends `Service`, such as `MyService`.

 - Set Service Name: `MyService._SERVICE_NAME = "myservice"`
 - Set Shutdown Time Limit: `MyService._SERVICE_SHUTDOWN_LIMIT = 300` (default shown)
 - Set Thread Check Interval: `MyService._SERVICE_CHECK_TIME = 0.5` (default shown)
 - Configuration Loading: Utilizes [`UtilFuncs.find_config_file()`](#find_config_file) and [`UtilFuncs.load_sfile()`](#load_sfile), will attempt to load `<service_name>/<service_name>.[toml,yaml,json]` from 'well known' paths, configuaration available in `MyService._config`. Additional locations can be added with [`UtilFuncs.add_config_search_path()`](#add_config_search_path)
 - Subscribe / Create Thread: `MyService.services += <function>`
 - Unsubscribe / Remove Thread: `MyService.services -= <function>`
 - Shutdown: Set `MyService.shutdown` (bool), Utilizes `Utilfuncs.shutdown`
 - Restart: Set `MyService.restart` (bool), Utilizes `Utilfuncs.restart`
 - Run Check: Check `MyService.should_run` to see if thread needs to stop
 - Run: Call `MyService.run()`
 - Stop: Call `MyService.stop()`
 - Signal Handlers: Utilizes [`Utilfuncs.register_signals()`](#register_signals)
 - Process ID storage: Set `pid_dir` in Configuration File

Example Service Functions:

```
import logging
from time import sleep

from atckit.service import Service

class MyService(Service):
    def __init__(self) -> None:
        super().__init__()
        self.services += self._testloopA # Add Thread to Service
        self.services += self._testloopB # Add another Thread

    def _testloopA(self) -> None:
        """Test Function, Continuous loop
        @retval None Nothing
        """
        while self.should_run:
            self.logger.info("Loop test")
            sleep(1)

    def _testloopB(self) -> None:
        """Test Function, One Shot, restarting at minimum every `MyService._SERVICE_CHECK_TIME` seconds
        @retval None Nothing
        """
        self.logger.info("Test Looop")
        sleep(1)

if __name__ == "__main__":
    logging.basicConfig(level=logging.DEBUG) # Logging Configuration
    service:MyService = MyService() # Initialize Service
    service.run() # Stop with ABRT/INT/TERM CTRL+C
    service.stop() # Cleanup / Wait for Shutdown
```

## Version (atckit.version)

A Class for version manipulation.

A Version can be created from:
 - Semantic String (`"1.0.0"`)
 - List of Strings or Ints of a version (`["1","0","0"]` or `[1,0,0]`)
 - Tuple of Strings or Ints of a version (`("1","0","0")` or `(1,0,0)`)

Versions are comparable (`>`,`<`,`>=`,`<=`,`==`,`!=`)
Versions are addable and subtractable (`a -= b`, `a += b`)
 - During subtraction, if a part goes negative, it will be set to 0

### Version Search Strings

To make Version things even easier, 2 functions are also included in the Version module, which enables a list of matching versions to be created, from the search.

Version Search Strings are 1 or more entries in a specially formatted string: `<comparator>:<version>,...`

Supported comparators: `>`,`<`,`>=`,`<=`,`==`,`!=`

Example Searches:

 - ">=:1.0.0,!=:2.0.2,<=:4.0.0"
 - "<=:3.0.0,>:0.9.0"

#### `version_locator`

Given a list of versions, locate a version which matches a given search string.

 - Example 1 matching:
   - Any Version Newer than 1.0.0, including 1.0.0
   - Not Version 2.0.2
   - Any Version Older than 4.0.0, including 4.0.0
 - Example 2 matching:
   - Any Version Older than 3.0.0, including 3.0.0
   - Any Version Newer than 0.9.0, not including 0.9.0

#### `version_search_merge`

Combine 2 Version Search Strings, creating a single string, which satisfies all searches in each string.

Given the examples above, merging these two searches, would result in the following compatible search: `>=:1.0.0,<=:3.0.0,!=:2.0.2`

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "atckit",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.11",
    "maintainer_email": null,
    "keywords": null,
    "author": null,
    "author_email": "AccidentallyTheCable <cableninja@cableninja.net>",
    "download_url": "https://files.pythonhosted.org/packages/d8/ab/48febe366249b98a95332e4030f042f3737c0e7b9bb59052a4b5f40a70c1/atckit-2.0.1.tar.gz",
    "platform": null,
    "description": "# ATCKit\n\nAccidentallyTheCable's Utility Kit\n\n- [ATCKit](#atckit)\n  - [About](#about)\n    - [How does it work?](#how-does-it-work)\n  - [Usage](#usage)\n    - [FunctionSubscriber (atckit.subscriber)](#functionsubscriber-atckitsubscriber)\n  - [Core Functions (atckit)](#core-functions-atckit)\n    - [`create_object_logger`](#create_object_logger)\n    - [`create_static_logger`](#create_static_logger)\n    - [`deltatime_str`](#deltatime_str)\n    - [`deep_sort`](#deep_sort)\n  - [File Utils (atckit.files)](#file-utils-atckitfiles)\n    - [`dump_sstr`](#dump_sstr)\n    - [`load_sstr`](#load_sstr)\n    - [`load_sfile`](#load_sfile)\n    - [`scan_dir`](#scan_dir)\n    - [`find_config_file`](#find_config_file)\n    - [`add_config_search_path`](#add_config_search_path)\n    - [`remove_config_search_path`](#remove_config_search_path)\n    - [`add_config_search_file_ext`](#add_config_search_file_ext)\n    - [`remove_config_search_file_ext`](#remove_config_search_file_ext)\n  - [Signals (atckit.signals)](#signals-atckitsignals)\n    - [`check_pid`](#check_pid)\n    - [`register_pid`](#register_pid)\n    - [`register_signals`](#register_signals)\n  - [Service (atckit.service)](#service-atckitservice)\n  - [Version (atckit.version)](#version-atckitversion)\n    - [Version Search Strings](#version-search-strings)\n      - [`version_locator`](#version_locator)\n      - [`version_search_merge`](#version_search_merge)\n\n## About\n\nThis is a small kit of classes, util functions, etc that I found myself rewriting or reusing frequently, and instead of copying everywhere, they are now here.\n\n> **WARNING**: Version 2.0 is a breaking change from 1.x versions\n>   2.0 Removes the static class and moves things around. Please check the docs below for where things are now\n\n### How does it work?\n\nDo the needfuls.... *do the needful dance*\n\nLiterally, import whatever you need to use..\n\n## Usage\n\n### FunctionSubscriber (atckit.subscriber)\n\nA Class container for Function callback subscription via `+=` or `-=`. Functions can be retrieved in order of addition.\n\n```\nsubscriber = FunctionSubscriber()\n\ndef a():\n    print(\"I am a teapot\")\n\ndef b():\n    print(\"I am definitely totally not also a teapot, I swear\")\n\nsubscriber += a\nsubscriber += b\n\nfor cb in subscriber.functions:\n    cb()\n\n>> I am a teapot\n>> I am definitely totally not also a teapot, I swear\n```\n\nThis class uses the `typing.Callable` type for function storage. You can extend the `FunctionSubscriber` class to define the\ncallback function parameters, etc.\n\n```\nclass MySubscriber(FunctionSubscriber):\n    \"\"\"My Function Subscriber\n    Callback: (bool) -> None\n    \"\"\"\n\n    _functions:list[Callable[[bool],None]]\n\n    def __iadd__(self,fn:Callable[[bool],None]) -> Self:\n        \"\"\"Inline Add. Subscribe Function\n        @param method \\c fn Method to Subscribe\n        \"\"\"\n        return super().__iadd__(fn)\n\n    def __isub__(self,fn:Callable[[bool],None]) -> Self:\n        \"\"\"Inline Subtract. Unsubscribe Function\n        @param method \\c fn Method to Unsubscribe\n        \"\"\"\n        return super().__isub__(fn)\n```\n\n## Core Functions (atckit)\n\n### `create_object_logger`\n\n Create `logging.Logger` instance for object specifically\n\n### `create_static_logger`\n\n Create `logging.Logger` instance of a specified name\n\n### `deltatime_str`\n\n Create `datetime.timedelta` from short formatted time string. Format: `0Y0M0w0d0h0m0s0ms`\n\n### `deep_sort`\n \n Sort a Dictionary recursively, including through lists of dicts\n\n## File Utils (atckit.files)\n\nClasses and functions located in the `files` module\n\n### `dump_sstr`\n \n Dump Structured Data (dict) to str of specified format. Accepts JSON, YAML, TOML\n\n### `load_sstr`\n\n Load Structured Data from String. Accepts JSON, YAML, TOML\n\n### `load_sfile`\n \n Load Structured Data from File, automatically determining data by file extension. Accepts JSON, YAML, TOML\n### `scan_dir`\n \n Search a specified Path, and execute a callback function on discovered files.\n   - Allows exclusion of Files/Dirs via regex pattern matching\n\n### `find_config_file`\n \n Look for config file in 'well defined' paths. Searches for `<service>/<config>.[toml,json,yaml]` in `~/.local/` and `/etc/` (in that order)\n\n### `add_config_search_path`\n \n Add Search Path for [`find_config_file`](#find_config_file)\n\n### `remove_config_search_path`\n \n Remove Search Path for [`find_config_file`](#find_config_file)\n\n### `add_config_search_file_ext`\n \n Add file extension for [`find_config_file`](#find_config_file)\n\n### `remove_config_search_file_ext`\n \n Remove file extension for [`find_config_file`](#find_config_file)\n\n## Signals (atckit.signals)\n\nSignal Handling functions located in `signals`\n\n### `check_pid`\n \n Check if a process ID exists (via kill 0)\n### `register_pid`\n \n Register (Write) process ID in specified directory as `<service>.pid`\n### `register_signals`\n \n Register Shutdown / Restart Handlers\n   - Check for Shutdown via UtilFuncs.shutdown (bool)\n   - Check for Restart via UtilFuncs.restart (bool)\n\n## Service (atckit.service)\n\nA Service / Daemon Class. Responds to signals properly, including HUP to restart threads\n\nHUP does not restart main thread. So if the main configuration file needs to be re-read, the service needs to be stopped and started completely.\n\nEntrypoint functions for services are defined under the `.services` [`FunctionSubscriber`](#FunctionSubscriber). These functions should be loopable, or be capable of starting again each time the function completes.\n\nCreate a class, which extends `Service`, such as `MyService`.\n\n - Set Service Name: `MyService._SERVICE_NAME = \"myservice\"`\n - Set Shutdown Time Limit: `MyService._SERVICE_SHUTDOWN_LIMIT = 300` (default shown)\n - Set Thread Check Interval: `MyService._SERVICE_CHECK_TIME = 0.5` (default shown)\n - Configuration Loading: Utilizes [`UtilFuncs.find_config_file()`](#find_config_file) and [`UtilFuncs.load_sfile()`](#load_sfile), will attempt to load `<service_name>/<service_name>.[toml,yaml,json]` from 'well known' paths, configuaration available in `MyService._config`. Additional locations can be added with [`UtilFuncs.add_config_search_path()`](#add_config_search_path)\n - Subscribe / Create Thread: `MyService.services += <function>`\n - Unsubscribe / Remove Thread: `MyService.services -= <function>`\n - Shutdown: Set `MyService.shutdown` (bool), Utilizes `Utilfuncs.shutdown`\n - Restart: Set `MyService.restart` (bool), Utilizes `Utilfuncs.restart`\n - Run Check: Check `MyService.should_run` to see if thread needs to stop\n - Run: Call `MyService.run()`\n - Stop: Call `MyService.stop()`\n - Signal Handlers: Utilizes [`Utilfuncs.register_signals()`](#register_signals)\n - Process ID storage: Set `pid_dir` in Configuration File\n\nExample Service Functions:\n\n```\nimport logging\nfrom time import sleep\n\nfrom atckit.service import Service\n\nclass MyService(Service):\n    def __init__(self) -> None:\n        super().__init__()\n        self.services += self._testloopA # Add Thread to Service\n        self.services += self._testloopB # Add another Thread\n\n    def _testloopA(self) -> None:\n        \"\"\"Test Function, Continuous loop\n        @retval None Nothing\n        \"\"\"\n        while self.should_run:\n            self.logger.info(\"Loop test\")\n            sleep(1)\n\n    def _testloopB(self) -> None:\n        \"\"\"Test Function, One Shot, restarting at minimum every `MyService._SERVICE_CHECK_TIME` seconds\n        @retval None Nothing\n        \"\"\"\n        self.logger.info(\"Test Looop\")\n        sleep(1)\n\nif __name__ == \"__main__\":\n    logging.basicConfig(level=logging.DEBUG) # Logging Configuration\n    service:MyService = MyService() # Initialize Service\n    service.run() # Stop with ABRT/INT/TERM CTRL+C\n    service.stop() # Cleanup / Wait for Shutdown\n```\n\n## Version (atckit.version)\n\nA Class for version manipulation.\n\nA Version can be created from:\n - Semantic String (`\"1.0.0\"`)\n - List of Strings or Ints of a version (`[\"1\",\"0\",\"0\"]` or `[1,0,0]`)\n - Tuple of Strings or Ints of a version (`(\"1\",\"0\",\"0\")` or `(1,0,0)`)\n\nVersions are comparable (`>`,`<`,`>=`,`<=`,`==`,`!=`)\nVersions are addable and subtractable (`a -= b`, `a += b`)\n - During subtraction, if a part goes negative, it will be set to 0\n\n### Version Search Strings\n\nTo make Version things even easier, 2 functions are also included in the Version module, which enables a list of matching versions to be created, from the search.\n\nVersion Search Strings are 1 or more entries in a specially formatted string: `<comparator>:<version>,...`\n\nSupported comparators: `>`,`<`,`>=`,`<=`,`==`,`!=`\n\nExample Searches:\n\n - \">=:1.0.0,!=:2.0.2,<=:4.0.0\"\n - \"<=:3.0.0,>:0.9.0\"\n\n#### `version_locator`\n\nGiven a list of versions, locate a version which matches a given search string.\n\n - Example 1 matching:\n   - Any Version Newer than 1.0.0, including 1.0.0\n   - Not Version 2.0.2\n   - Any Version Older than 4.0.0, including 4.0.0\n - Example 2 matching:\n   - Any Version Older than 3.0.0, including 3.0.0\n   - Any Version Newer than 0.9.0, not including 0.9.0\n\n#### `version_search_merge`\n\nCombine 2 Version Search Strings, creating a single string, which satisfies all searches in each string.\n\nGiven the examples above, merging these two searches, would result in the following compatible search: `>=:1.0.0,<=:3.0.0,!=:2.0.2`\n",
    "bugtrack_url": null,
    "license": "GPLv3",
    "summary": "AccidentallyTheCables Utility Kit",
    "version": "2.0.1",
    "project_urls": {
        "Bug Tracker": "https://gitlab.com/accidentallythecable-public/python-modules/python-atckit/issues",
        "Homepage": "https://gitlab.com/accidentallythecable-public/python-modules/python-atckit/"
    },
    "split_keywords": [],
    "urls": [
        {
            "comment_text": "atckit, Version: 2.0.1",
            "digests": {
                "blake2b_256": "8fbd94cb20177fcd6c7e15be01b1ca504619cd9d5e15d8bab2463a4bdc93bf25",
                "md5": "46d76237d0bf7b3499aa3cadbaea6076",
                "sha256": "b66c07fa71463f772a5fd19df23387de38085e6f73d20ec3c5df7bb95131679c"
            },
            "downloads": -1,
            "filename": "atckit-2.0.1-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "46d76237d0bf7b3499aa3cadbaea6076",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.11",
            "size": 28356,
            "upload_time": "2025-07-11T02:19:50",
            "upload_time_iso_8601": "2025-07-11T02:19:50.427786Z",
            "url": "https://files.pythonhosted.org/packages/8f/bd/94cb20177fcd6c7e15be01b1ca504619cd9d5e15d8bab2463a4bdc93bf25/atckit-2.0.1-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "atckit, Version: 2.0.1",
            "digests": {
                "blake2b_256": "d8ab48febe366249b98a95332e4030f042f3737c0e7b9bb59052a4b5f40a70c1",
                "md5": "0f6d67cb194ceeb9893646bc3545c9b4",
                "sha256": "85c87581523dc42819e85e4ea719d4c8cd6703aeebe3344df8926b89a2ef9d4b"
            },
            "downloads": -1,
            "filename": "atckit-2.0.1.tar.gz",
            "has_sig": false,
            "md5_digest": "0f6d67cb194ceeb9893646bc3545c9b4",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.11",
            "size": 92183,
            "upload_time": "2025-07-11T02:19:51",
            "upload_time_iso_8601": "2025-07-11T02:19:51.749345Z",
            "url": "https://files.pythonhosted.org/packages/d8/ab/48febe366249b98a95332e4030f042f3737c0e7b9bb59052a4b5f40a70c1/atckit-2.0.1.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-07-11 02:19:51",
    "github": false,
    "gitlab": true,
    "bitbucket": false,
    "codeberg": false,
    "gitlab_user": "accidentallythecable-public",
    "gitlab_project": "python-modules",
    "lcname": "atckit"
}
        
Elapsed time: 0.89629s