tsmetallic 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,330 @@
1
+ [
2
+ {
3
+ "label": "Subnet",
4
+ "importPath": "ipamanager.subnet",
5
+ "description": "ipamanager.subnet",
6
+ "isExtraImport": true,
7
+ "detail": "ipamanager.subnet",
8
+ "documentation": {}
9
+ },
10
+ {
11
+ "label": "get_term_size",
12
+ "importPath": "ipamanager.utils",
13
+ "description": "ipamanager.utils",
14
+ "isExtraImport": true,
15
+ "detail": "ipamanager.utils",
16
+ "documentation": {}
17
+ },
18
+ {
19
+ "label": "err_and_exit",
20
+ "importPath": "ipamanager.utils",
21
+ "description": "ipamanager.utils",
22
+ "isExtraImport": true,
23
+ "detail": "ipamanager.utils",
24
+ "documentation": {}
25
+ },
26
+ {
27
+ "label": "show_help",
28
+ "importPath": "ipamanager.utils",
29
+ "description": "ipamanager.utils",
30
+ "isExtraImport": true,
31
+ "detail": "ipamanager.utils",
32
+ "documentation": {}
33
+ },
34
+ {
35
+ "label": "err_and_exit",
36
+ "importPath": "ipamanager.utils",
37
+ "description": "ipamanager.utils",
38
+ "isExtraImport": true,
39
+ "detail": "ipamanager.utils",
40
+ "documentation": {}
41
+ },
42
+ {
43
+ "label": "sys",
44
+ "kind": 6,
45
+ "isExtraImport": true,
46
+ "importPath": "sys",
47
+ "description": "sys",
48
+ "detail": "sys",
49
+ "documentation": {}
50
+ },
51
+ {
52
+ "label": "json",
53
+ "kind": 6,
54
+ "isExtraImport": true,
55
+ "importPath": "json",
56
+ "description": "json",
57
+ "detail": "json",
58
+ "documentation": {}
59
+ },
60
+ {
61
+ "label": "Console",
62
+ "importPath": "rich.console",
63
+ "description": "rich.console",
64
+ "isExtraImport": true,
65
+ "detail": "rich.console",
66
+ "documentation": {}
67
+ },
68
+ {
69
+ "label": "Console",
70
+ "importPath": "rich.console",
71
+ "description": "rich.console",
72
+ "isExtraImport": true,
73
+ "detail": "rich.console",
74
+ "documentation": {}
75
+ },
76
+ {
77
+ "label": "settings",
78
+ "importPath": "ipamanager.config",
79
+ "description": "ipamanager.config",
80
+ "isExtraImport": true,
81
+ "detail": "ipamanager.config",
82
+ "documentation": {}
83
+ },
84
+ {
85
+ "label": "IpamDb",
86
+ "importPath": "ipamanager.db",
87
+ "description": "ipamanager.db",
88
+ "isExtraImport": true,
89
+ "detail": "ipamanager.db",
90
+ "documentation": {}
91
+ },
92
+ {
93
+ "label": "Report",
94
+ "importPath": "ipamanager.reports",
95
+ "description": "ipamanager.reports",
96
+ "isExtraImport": true,
97
+ "detail": "ipamanager.reports",
98
+ "documentation": {}
99
+ },
100
+ {
101
+ "label": "ReportRecord",
102
+ "importPath": "ipamanager.reports",
103
+ "description": "ipamanager.reports",
104
+ "isExtraImport": true,
105
+ "detail": "ipamanager.reports",
106
+ "documentation": {}
107
+ },
108
+ {
109
+ "label": "os",
110
+ "kind": 6,
111
+ "isExtraImport": true,
112
+ "importPath": "os",
113
+ "description": "os",
114
+ "detail": "os",
115
+ "documentation": {}
116
+ },
117
+ {
118
+ "label": "Path",
119
+ "importPath": "pathlib",
120
+ "description": "pathlib",
121
+ "isExtraImport": true,
122
+ "detail": "pathlib",
123
+ "documentation": {}
124
+ },
125
+ {
126
+ "label": "sqlite3",
127
+ "kind": 6,
128
+ "isExtraImport": true,
129
+ "importPath": "sqlite3",
130
+ "description": "sqlite3",
131
+ "detail": "sqlite3",
132
+ "documentation": {}
133
+ },
134
+ {
135
+ "label": "time",
136
+ "kind": 6,
137
+ "isExtraImport": true,
138
+ "importPath": "time",
139
+ "description": "time",
140
+ "detail": "time",
141
+ "documentation": {}
142
+ },
143
+ {
144
+ "label": "Table",
145
+ "importPath": "rich.table",
146
+ "description": "rich.table",
147
+ "isExtraImport": true,
148
+ "detail": "rich.table",
149
+ "documentation": {}
150
+ },
151
+ {
152
+ "label": "Panel",
153
+ "importPath": "rich.panel",
154
+ "description": "rich.panel",
155
+ "isExtraImport": true,
156
+ "detail": "rich.panel",
157
+ "documentation": {}
158
+ },
159
+ {
160
+ "label": "tsm.semantic_colors",
161
+ "kind": 6,
162
+ "isExtraImport": true,
163
+ "importPath": "tsm.semantic_colors",
164
+ "description": "tsm.semantic_colors",
165
+ "detail": "tsm.semantic_colors",
166
+ "documentation": {}
167
+ },
168
+ {
169
+ "label": "AddressPool",
170
+ "kind": 6,
171
+ "importPath": "src.tsm.ipamanager.address_pool",
172
+ "description": "src.tsm.ipamanager.address_pool",
173
+ "peekOfCode": "class AddressPool:\n \"\"\"A contiguous range of IP addresses intended to provide logical\n boundaries for automated allocation and reporting.\n \"\"\"\n def list_records(self):\n \"\"\"List detail records of members of the pool.\n Returns:\n list: A list of tuples, each a detail record of a member of the\n pool.\n \"\"\"",
174
+ "detail": "src.tsm.ipamanager.address_pool",
175
+ "documentation": {}
176
+ },
177
+ {
178
+ "label": "cli_parser",
179
+ "kind": 2,
180
+ "importPath": "src.tsm.ipamanager.cli",
181
+ "description": "src.tsm.ipamanager.cli",
182
+ "peekOfCode": "def cli_parser(args=sys.argv):\n \"\"\"Handles unpacking of sys.argv.\n Args:\n args (list): List of arguments received by the module at runtime.\n Defaults to sys.argv.\n Returns:\n tuple: a subcommand, which selects the function to be called by the\n CLI, and a list of parameters to be made available to that function.\n \"\"\"\n if len(args) == 0:",
183
+ "detail": "src.tsm.ipamanager.cli",
184
+ "documentation": {}
185
+ },
186
+ {
187
+ "label": "cli",
188
+ "kind": 2,
189
+ "importPath": "src.tsm.ipamanager.cli",
190
+ "description": "src.tsm.ipamanager.cli",
191
+ "peekOfCode": "def cli():\n \"\"\"Calls functions based on the arguments used to run the application.\"\"\"\n subcmd, params = cli_parser()\n dbpath = config[\"database\"][\"path\"]\n dbfields = config[\"database\"][\"fields\"]\n db = IpamDb(str(dbpath))\n match subcmd:\n case \"available\":\n print(db.is_unassigned(params[0]))\n case \"setup\":",
192
+ "detail": "src.tsm.ipamanager.cli",
193
+ "documentation": {}
194
+ },
195
+ {
196
+ "label": "dump_config",
197
+ "kind": 2,
198
+ "importPath": "src.tsm.ipamanager.config",
199
+ "description": "src.tsm.ipamanager.config",
200
+ "peekOfCode": "def dump_config():\n print(json.dumps(settings))",
201
+ "detail": "src.tsm.ipamanager.config",
202
+ "documentation": {}
203
+ },
204
+ {
205
+ "label": "config_file",
206
+ "kind": 5,
207
+ "importPath": "src.tsm.ipamanager.config",
208
+ "description": "src.tsm.ipamanager.config",
209
+ "peekOfCode": "config_file = Path(os.environ.get(\"XDG_CONFIG_HOME\")) / \"ipam/settings.conf\"\nif not config_file.exists():\n err_and_exit(f\"Missing Config - {config_file}\")\nwith open(config_file, \"r\", encoding=\"utf-8\") as f:\n settings = json.load(f)\ndef dump_config():\n print(json.dumps(settings))",
210
+ "detail": "src.tsm.ipamanager.config",
211
+ "documentation": {}
212
+ },
213
+ {
214
+ "label": "Database",
215
+ "kind": 6,
216
+ "importPath": "src.tsm.ipamanager.db",
217
+ "description": "src.tsm.ipamanager.db",
218
+ "peekOfCode": "class Database:\n \"\"\"Handles database operations for SQLite3.\n Provides methods for connecting to the database, executing SQL queries,\n and fetching results. This class is intended to be used as a base class\n for specific database functionality.\n Attributes:\n db_name (str): The name of the SQLite3 database file.\n conn (sqlite3.Connection): The database connection object.\n \"\"\"\n def __init__(self, db_name):",
219
+ "detail": "src.tsm.ipamanager.db",
220
+ "documentation": {}
221
+ },
222
+ {
223
+ "label": "IpamDb",
224
+ "kind": 6,
225
+ "importPath": "src.tsm.ipamanager.db",
226
+ "description": "src.tsm.ipamanager.db",
227
+ "peekOfCode": "class IpamDb(Database):\n \"\"\"Specialized database class for managing IP address data.\n This class extends the Database class and provides additional functionality\n for managing IP address records, such as assigning, unassigning, and\n retrieving IP addresses.\n Attributes:\n main_table (dict): A dictionary containing the structure of the IP\n address table.\n \"\"\"\n def __init__(self, db_name):",
228
+ "detail": "src.tsm.ipamanager.db",
229
+ "documentation": {}
230
+ },
231
+ {
232
+ "label": "ReportRecord",
233
+ "kind": 6,
234
+ "importPath": "src.tsm.ipamanager.reports",
235
+ "description": "src.tsm.ipamanager.reports",
236
+ "peekOfCode": "class ReportRecord:\n def __init__(self, addr_id, address, host, pool):\n hstyle_dimmed = \"grey23\"\n hstyle_bright = \"sky_blue3\"\n pstyle = {\n \"none\": \"color(238)\",\n \"oleander\": \"color(88)\",\n \"dahlia\": \"color(91)\",\n \"hemlock\": \"color(98)\",\n \"dhcp\": \"color(141)\"",
237
+ "detail": "src.tsm.ipamanager.reports",
238
+ "documentation": {}
239
+ },
240
+ {
241
+ "label": "Report",
242
+ "kind": 6,
243
+ "importPath": "src.tsm.ipamanager.reports",
244
+ "description": "src.tsm.ipamanager.reports",
245
+ "peekOfCode": "class Report:\n def __init__(self, name, fields, records):\n self.console = Console()\n self.name = name\n self.fields = fields\n self.records = records\n def display_table(self):\n table = Table(title=self.name,\n padding=(0, 4),\n expand=False,",
246
+ "detail": "src.tsm.ipamanager.reports",
247
+ "documentation": {}
248
+ },
249
+ {
250
+ "label": "read_file",
251
+ "kind": 2,
252
+ "importPath": "src.tsm.ipamanager.utils",
253
+ "description": "src.tsm.ipamanager.utils",
254
+ "peekOfCode": "def read_file(filepath):\n \"\"\"Reads and returns the content of a file.\"\"\"\n try:\n with open(filepath, 'r') as file:\n return file.read()\n except FileNotFoundError:\n return None\ndef write_file(filepath, content):\n \"\"\"Writes content to a file.\"\"\"\n try:",
255
+ "detail": "src.tsm.ipamanager.utils",
256
+ "documentation": {}
257
+ },
258
+ {
259
+ "label": "write_file",
260
+ "kind": 2,
261
+ "importPath": "src.tsm.ipamanager.utils",
262
+ "description": "src.tsm.ipamanager.utils",
263
+ "peekOfCode": "def write_file(filepath, content):\n \"\"\"Writes content to a file.\"\"\"\n try:\n with open(filepath, 'w') as file:\n file.write(content)\n return True\n except Exception as e:\n print(f\"Error writing to file: {e}\")\n return False\ndef create_directory(dirpath):",
264
+ "detail": "src.tsm.ipamanager.utils",
265
+ "documentation": {}
266
+ },
267
+ {
268
+ "label": "create_directory",
269
+ "kind": 2,
270
+ "importPath": "src.tsm.ipamanager.utils",
271
+ "description": "src.tsm.ipamanager.utils",
272
+ "peekOfCode": "def create_directory(dirpath):\n \"\"\"Creates a directory if it doesn't exist.\"\"\"\n if not os.path.exists(dirpath):\n os.makedirs(dirpath)\n return True\n return False\ndef load_json(filepath):\n \"\"\"Loads JSON data from a file.\"\"\"\n try:\n with open(filepath, 'r') as file:",
273
+ "detail": "src.tsm.ipamanager.utils",
274
+ "documentation": {}
275
+ },
276
+ {
277
+ "label": "load_json",
278
+ "kind": 2,
279
+ "importPath": "src.tsm.ipamanager.utils",
280
+ "description": "src.tsm.ipamanager.utils",
281
+ "peekOfCode": "def load_json(filepath):\n \"\"\"Loads JSON data from a file.\"\"\"\n try:\n with open(filepath, 'r') as file:\n return json.load(file)\n except (FileNotFoundError, json.JSONDecodeError):\n return None\ndef save_json(filepath, data, indent=4):\n \"\"\"Saves data to a JSON file.\"\"\"\n try:",
282
+ "detail": "src.tsm.ipamanager.utils",
283
+ "documentation": {}
284
+ },
285
+ {
286
+ "label": "save_json",
287
+ "kind": 2,
288
+ "importPath": "src.tsm.ipamanager.utils",
289
+ "description": "src.tsm.ipamanager.utils",
290
+ "peekOfCode": "def save_json(filepath, data, indent=4):\n \"\"\"Saves data to a JSON file.\"\"\"\n try:\n with open(filepath, 'w') as file:\n json.dump(data, file, indent=indent)\n return True\n except Exception as e:\n print(f\"Error saving JSON to file: {e}\")\n return False\ndef get_term_size():",
291
+ "detail": "src.tsm.ipamanager.utils",
292
+ "documentation": {}
293
+ },
294
+ {
295
+ "label": "get_term_size",
296
+ "kind": 2,
297
+ "importPath": "src.tsm.ipamanager.utils",
298
+ "description": "src.tsm.ipamanager.utils",
299
+ "peekOfCode": "def get_term_size():\n \"\"\"Return terminal dimensions (width, height)\"\"\"\n try:\n terminal_size = os.get_terminal_size()\n width = terminal_size.columns\n height = terminal_size.lines\n return width, height\n except Exception as e:\n print(f\"Error getting response from terminal: {e}\")\n return False",
300
+ "detail": "src.tsm.ipamanager.utils",
301
+ "documentation": {}
302
+ },
303
+ {
304
+ "label": "err_and_exit",
305
+ "kind": 2,
306
+ "importPath": "src.tsm.ipamanager.utils",
307
+ "description": "src.tsm.ipamanager.utils",
308
+ "peekOfCode": "def err_and_exit(message):\n \"\"\"Print error message and exit.\n Args:\n message (string): Info to be displayed before exiting. Usually\n describes the error or reason for the exit.\n \"\"\"\n sys.exit(print(f\"\\001\\033[31m\\002Error:\\001\\033[m\\002 {message}\"))\ndef show_help():\n \"\"\"Display a help message with a list of CLI subcommands.\"\"\"\n print(\"\\nUsage: ipm SUBCOMMAND [ARG[S]]\\n\")",
309
+ "detail": "src.tsm.ipamanager.utils",
310
+ "documentation": {}
311
+ },
312
+ {
313
+ "label": "show_help",
314
+ "kind": 2,
315
+ "importPath": "src.tsm.ipamanager.utils",
316
+ "description": "src.tsm.ipamanager.utils",
317
+ "peekOfCode": "def show_help():\n \"\"\"Display a help message with a list of CLI subcommands.\"\"\"\n print(\"\\nUsage: ipm SUBCOMMAND [ARG[S]]\\n\")\n print(\"Subcommand:\")\n print(\" setup - Initialize database (no assignments)\")\n print(\" assign - Assign ADDRESS to HOST\")\n print(\" next - Assign next available IP in POOL to HOST\")\n print(\" show - Print details (host, pool) for ADDRESS\")\n print(\" list - Show all IPs, host assignments and pools\")\n print(\" help - Display this message\")",
318
+ "detail": "src.tsm.ipamanager.utils",
319
+ "documentation": {}
320
+ },
321
+ {
322
+ "label": "scolor",
323
+ "kind": 5,
324
+ "importPath": "app",
325
+ "description": "app",
326
+ "peekOfCode": "scolor = sc.SemanticColor(\"external\")\nprint(scolor)",
327
+ "detail": "app",
328
+ "documentation": {}
329
+ }
330
+ ]
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 tsharley
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,98 @@
1
+ .DELETE_ON_ERROR:
2
+ .ONESHELL:
3
+ .SHELLFLAGS := -eu -o pipefail -c
4
+ MAKEFLAGS += --warn-undefined-variables
5
+ MAKEFLAGS += --no-builtin-rules
6
+ SHELL := bash
7
+
8
+ .PHONY: help tree init run view clean debug build install config
9
+
10
+ .DEFAULT_GOAL := help
11
+
12
+ VENV = .venv
13
+ ACTIVATE = $(VENV)/bin/activate
14
+ PIP = $(VENV)/bin/pip
15
+ PYTHON = $(VENV)/bin/python
16
+ BREWPYTHON = /opt/homebrew/bin/python3
17
+ PYGITIGNORE = $(HOME)/Repos/gitignore/Python.gitignore
18
+ GIT_ORG = ts
19
+ GIT_REPO = $$(basename $$(pwd))
20
+ GIT_URL = http://git.tsmetallic.com/$(GIT_ORG)/$(GIT_REPO)
21
+ PACKAGE = tsm
22
+ PROJECT = tsm
23
+
24
+
25
+ help:
26
+ @echo -e ""
27
+ @echo -e " Help - Available Make Commands"
28
+ @echo -e " ====================================================================="
29
+ @echo -e ""
30
+ @echo -e " Setup Commands:"
31
+ @echo -e ""
32
+ @echo -e " make init : Initialize version control and link remote"
33
+ @echo -e " make install : Install pkg/module in local environment"
34
+ @echo -e ""
35
+ @echo -e " Dev Commands:"
36
+ @echo -e ""
37
+ @echo -e " make tree : Show project's file system excluding caches"
38
+ @echo -e " make view : Display a snapshot of each database table"
39
+ @echo -e " make config : Display runtime configuration"
40
+ @echo -e " make debug : Toggle the configuration option for debug mode"
41
+ @echo -e ""
42
+ @echo -e " Test Commands:"
43
+ @echo -e ""
44
+ @echo -e " make test : Execute full test suite"
45
+ @echo -e " make utest : Execute unit tests"
46
+ @echo -e " make itest : Execute integration tests"
47
+ @echo -e ""
48
+ @echo -e " Build Commands:"
49
+ @echo -e ""
50
+ @echo -e " make build : Compile project to wheel distribution"
51
+ @echo -e " make clean : Remove cache files and build artifacts"
52
+ @echo -e ""
53
+
54
+ tree:
55
+ @tree -I "*.egg-info|__pycache__"
56
+
57
+ config: config.json
58
+ @cat config.json | jq
59
+
60
+ debug:
61
+ @/Users/tjsharley/Projects/tsm/.venv/bin/tsm-tgl
62
+
63
+ view:
64
+ @source $(ACTIVATE) && $(PYTHON) src/heroes/view.py database.db team
65
+ @source $(ACTIVATE) && $(PYTHON) src/heroes/view.py database.db hero
66
+
67
+ build: $(ACTIVATE)
68
+ @source $(ACTIVATE) && $(PYTHON) -m build
69
+
70
+ install: $(ACTIVATE)
71
+ @source $(ACTIVATE) && $(PYTHON) -m pip install -e .
72
+
73
+ run: $(ACTIVATE) $(EPSCRIPT)
74
+ @source $(ACTIVATE) && $(EPSCRIPT)
75
+
76
+ $(ACTIVATE): requirements.txt
77
+ @$(BREWPYTHON) -m venv $(VENV)
78
+ @$(PYTHON) -m pip install --upgrade pip
79
+ @$(PIP) install -r requirements.txt
80
+
81
+ clean:
82
+ @rm -rf src/$(PKGNAME)/__pycache__
83
+ @rm -rf proc/out/*
84
+
85
+ requirements.txt:
86
+ @pip freeze --exclude-editable >> 'requirements.txt'
87
+
88
+ init: .git
89
+
90
+ .git: .gitignore
91
+ @git init .
92
+ @git add .
93
+ @git commit -a -m 'Initial commit'
94
+ @git remote add origin $(GIT_URL)
95
+ @git push -u origin main
96
+
97
+ .gitignore:
98
+ @cp $(PYGITIGNORE) .gitignore
@@ -0,0 +1,82 @@
1
+ Metadata-Version: 2.4
2
+ Name: tsmetallic
3
+ Version: 1.0.0
4
+ Summary: A collection of reusable Python utilities and personal libraries.
5
+ Author-email: tsmetallic <ts-metallic@proton.me>
6
+ Description-Content-Type: text/markdown
7
+ License-Expression: MIT
8
+ License-File: LICENSE
9
+
10
+ # tsmetallic
11
+ > A lightweight collection of reusable Python utilities for building robust applications.
12
+ `tsmetallic` is a modular utility library that provides the infrastructure code commonly needed across Python projects. It brings together reusable components for logging, configuration management, data loading, registries, dispatchers, and other application building blocks into a single, consistent package.
13
+ Rather than being a framework, `tsmetallic` is designed to complement your existing architecture by providing small, focused utilities that solve common problems with minimal dependencies.
14
+
15
+ ## Installation
16
+ ```bash
17
+ python -m venv .venv
18
+ source .venv/bin/activate
19
+ pip install tsmetallic
20
+ ```
21
+
22
+ ## Why tsmetallic?
23
+ Most Python applications eventually grow a collection of shared infrastructure:
24
+ * Logging configuration
25
+ * Configuration loading
26
+ * File and data loaders
27
+ * Registry and dispatcher patterns
28
+ * General-purpose helper utilities
29
+ Instead of rewriting these components for every project, `tsm` packages them into reusable modules with a consistent API.
30
+
31
+ ## Features
32
+ * **Modular** — Import only the functionality you need.
33
+ * **Lightweight** — Designed to stay out of your way.
34
+ * **Reusable** — Components intended for use across many projects.
35
+ * **Practical** — Focused on solving common application problems.
36
+ * **Well-typed** — Modern Python with type annotations where appropriate.
37
+
38
+ ## Included Modules
39
+ Although the library continues to evolve, it currently includes utilities such as:
40
+
41
+ | Module | Purpose |
42
+ | --------------------- | ------------------------------------ |
43
+ | `tsmetallic.logging` | Logging configuration and helpers |
44
+ | `tsmetallic.config` | Configuration loading and management |
45
+ | `tsmetallic.loaders` | Common data and file loaders |
46
+ | `tsmetallic.registry` | Generic registry implementations |
47
+ | `tsmetallic.utils` | General-purpose utility functions |
48
+
49
+ Additional modules are added as reusable functionality emerges from real-world projects.
50
+
51
+ ## Example
52
+ ```python
53
+ import tsmetallic.logging
54
+
55
+ logger = tsmetallic.logging.setup_logging()
56
+ logger.info('Logging initialized')
57
+ ```
58
+ Because each module is independent, you can adopt only the pieces that fit your application.
59
+
60
+ ## Design Principles
61
+ `tsmetallic` is built around a few simple ideas:
62
+ * Prefer composition over frameworks.
63
+ * Keep abstractions small and focused.
64
+ * Minimize dependencies.
65
+ * Write code that is easy to understand and reuse.
66
+ * Favor explicit behavior over hidden magic.
67
+
68
+ ## Compatibility
69
+ * Python >= 3.12
70
+ * macOS
71
+ * Linux
72
+ * Windows?
73
+
74
+ ## Documentation
75
+ Each module includes docstrings and examples. As the package grows, additional documentation and API references will be added.
76
+
77
+ ## Contributing
78
+ Issues, bug reports, and suggestions are welcome. While `tsm` primarily serves as a personal utility library, improvements that make the package more generally useful are appreciated.
79
+
80
+ ## License
81
+ Released under the terms of the project's license. See `LICENSE` for details.
82
+
@@ -0,0 +1,72 @@
1
+ # tsmetallic
2
+ > A lightweight collection of reusable Python utilities for building robust applications.
3
+ `tsmetallic` is a modular utility library that provides the infrastructure code commonly needed across Python projects. It brings together reusable components for logging, configuration management, data loading, registries, dispatchers, and other application building blocks into a single, consistent package.
4
+ Rather than being a framework, `tsmetallic` is designed to complement your existing architecture by providing small, focused utilities that solve common problems with minimal dependencies.
5
+
6
+ ## Installation
7
+ ```bash
8
+ python -m venv .venv
9
+ source .venv/bin/activate
10
+ pip install tsmetallic
11
+ ```
12
+
13
+ ## Why tsmetallic?
14
+ Most Python applications eventually grow a collection of shared infrastructure:
15
+ * Logging configuration
16
+ * Configuration loading
17
+ * File and data loaders
18
+ * Registry and dispatcher patterns
19
+ * General-purpose helper utilities
20
+ Instead of rewriting these components for every project, `tsm` packages them into reusable modules with a consistent API.
21
+
22
+ ## Features
23
+ * **Modular** — Import only the functionality you need.
24
+ * **Lightweight** — Designed to stay out of your way.
25
+ * **Reusable** — Components intended for use across many projects.
26
+ * **Practical** — Focused on solving common application problems.
27
+ * **Well-typed** — Modern Python with type annotations where appropriate.
28
+
29
+ ## Included Modules
30
+ Although the library continues to evolve, it currently includes utilities such as:
31
+
32
+ | Module | Purpose |
33
+ | --------------------- | ------------------------------------ |
34
+ | `tsmetallic.logging` | Logging configuration and helpers |
35
+ | `tsmetallic.config` | Configuration loading and management |
36
+ | `tsmetallic.loaders` | Common data and file loaders |
37
+ | `tsmetallic.registry` | Generic registry implementations |
38
+ | `tsmetallic.utils` | General-purpose utility functions |
39
+
40
+ Additional modules are added as reusable functionality emerges from real-world projects.
41
+
42
+ ## Example
43
+ ```python
44
+ import tsmetallic.logging
45
+
46
+ logger = tsmetallic.logging.setup_logging()
47
+ logger.info('Logging initialized')
48
+ ```
49
+ Because each module is independent, you can adopt only the pieces that fit your application.
50
+
51
+ ## Design Principles
52
+ `tsmetallic` is built around a few simple ideas:
53
+ * Prefer composition over frameworks.
54
+ * Keep abstractions small and focused.
55
+ * Minimize dependencies.
56
+ * Write code that is easy to understand and reuse.
57
+ * Favor explicit behavior over hidden magic.
58
+
59
+ ## Compatibility
60
+ * Python >= 3.12
61
+ * macOS
62
+ * Linux
63
+ * Windows?
64
+
65
+ ## Documentation
66
+ Each module includes docstrings and examples. As the package grows, additional documentation and API references will be added.
67
+
68
+ ## Contributing
69
+ Issues, bug reports, and suggestions are welcome. While `tsm` primarily serves as a personal utility library, improvements that make the package more generally useful are appreciated.
70
+
71
+ ## License
72
+ Released under the terms of the project's license. See `LICENSE` for details.
@@ -0,0 +1,14 @@
1
+ [build-system]
2
+ requires = ["flit_core >=3.11,<4"]
3
+ build-backend = "flit_core.buildapi"
4
+
5
+ [project]
6
+ name = "tsmetallic"
7
+ authors = [{name = "tsmetallic", email = "ts-metallic@proton.me"}]
8
+ readme = "README.md"
9
+ license = "MIT"
10
+ license-files = ["LICENSE"]
11
+ dynamic = ["version", "description"]
12
+
13
+ # [tool.flit.packages.find]
14
+ # where = ["src"]
@@ -0,0 +1,20 @@
1
+ attrs==26.1.0
2
+ build==1.3.0
3
+ cyclopts==4.21.1
4
+ docstring_parser==0.18.0
5
+ iniconfig==2.3.0
6
+ markdown-it-py==4.0.0
7
+ mdurl==0.1.2
8
+ packaging==25.0
9
+ pluggy==1.6.0
10
+ prompt_toolkit==3.0.52
11
+ Pygments==2.19.2
12
+ pyproject_hooks==1.2.0
13
+ pytest==9.1.1
14
+ python-json-logger==4.1.0
15
+ PyYAML==6.0.3
16
+ questionary==2.1.1
17
+ rich==14.1.0
18
+ rich-rst==2.1.0
19
+ setuptools==80.9.0
20
+ wcwidth==0.2.14
@@ -0,0 +1,8 @@
1
+ import tsm.config
2
+ import tsm.loader
3
+
4
+
5
+ def test_config_reads_file():
6
+ loader = tsm.loader.DataLoader.get_loader("json")
7
+ config = loader.load(loader, 'settings.json')
8
+ assert config["logging"]["console"] == True
@@ -0,0 +1,5 @@
1
+ # import tsm.logging
2
+
3
+ # def test_logger_writes_to_console():
4
+ # logger = tsm.logging.setup_logging()
5
+ # assert logger.info('test').endswith('test')
@@ -0,0 +1,2 @@
1
+ """A collection of reusable Python utilities and personal libraries."""
2
+ __version__ = '1.0.0'
@@ -0,0 +1,29 @@
1
+ from pathlib import Path
2
+ from typing import Any
3
+ import tsm.loader
4
+
5
+
6
+ class Config:
7
+
8
+ paths = ['.', './config']
9
+ names = ['config', 'settings']
10
+ extensions = ['json', 'toml', 'yaml', 'yml']
11
+
12
+ def __init__(self, path: Path | None = None) -> None:
13
+ if path is not None and Path(path).exists():
14
+ self.path = Path(path)
15
+ else:
16
+ for path in self.paths:
17
+ for name in self.names:
18
+ for extension in self.extensions:
19
+ if not (Path(path) / f'{name}.{extension}').exists():
20
+ continue
21
+ else:
22
+ self.path = Path(path) / f'{name}.{extension}'
23
+
24
+ def load(self) -> dict[str, Any]:
25
+ print(f'loader_path: {self.path}')
26
+ filetype = self.path.suffix.replace('.', '')
27
+ loader = tsm.loader.DataLoader.get_loader(filetype)
28
+ print(f'loader.load({self.path})')
29
+ loader.load(self.path)
@@ -0,0 +1,52 @@
1
+ class DataLoader:
2
+ _registry = {}
3
+
4
+ def __init_subclass__(cls, fmt=None, **kwargs):
5
+ super().__init_subclass__(**kwargs)
6
+ if fmt:
7
+ DataLoader._registry[fmt] = cls
8
+
9
+ @classmethod
10
+ def get_loader(cls, fmt):
11
+ if fmt not in cls._registry:
12
+ raise ValueError(f"No loader for {fmt!r}. "
13
+ f"Available: {list(cls._registry)}")
14
+ return cls._registry[fmt]
15
+
16
+
17
+ class CSVLoader(DataLoader, fmt="csv"):
18
+
19
+ def load(self, path):
20
+ import csv
21
+ return f"Loading CSV from {path}"
22
+
23
+
24
+ class JSONLoader(DataLoader, fmt="json"):
25
+
26
+ def load(self, path):
27
+ import json
28
+ with open(path, 'r', encoding='utf-8') as file:
29
+ data = json.load(file)
30
+ return data
31
+
32
+
33
+ class YAMLLoader(DataLoader, fmt="yaml"):
34
+
35
+ def load(self, path):
36
+ import yaml
37
+ with open(path, 'rb') as file:
38
+ data = yaml.safe_load(file)
39
+ return data
40
+
41
+
42
+ class TOMLLoader(DataLoader, fmt="toml"):
43
+
44
+ def load(self, path):
45
+ import tomllib
46
+ with open(path, 'rb') as file:
47
+ data = tomllib.load(file)
48
+ return data
49
+
50
+
51
+ # loader = DataLoader.get_loader("parquet")
52
+ # print(loader.load("sales.parquet"))
@@ -0,0 +1,75 @@
1
+ import logging
2
+ import logging.handlers
3
+ from enum import Enum
4
+
5
+
6
+ class LogLevel(Enum):
7
+ NOTSET = 0
8
+ DEBUG = 10
9
+ INFO = 20
10
+ WARN = 30
11
+ ERROR = 40
12
+ CRITICAL = 50
13
+
14
+
15
+ def setup_logging(
16
+ *,
17
+ name: str = __name__,
18
+ console: bool = True,
19
+ path: str | None = None,
20
+ level: LogLevel = 20,
21
+ ):
22
+ """Provides a logger with optional FileHandler and StreamHandler.
23
+
24
+ Parameters
25
+ ----------
26
+ name: str
27
+ The new logger's name (default: __name__)
28
+ console: bool
29
+ Whether or not to activate the console stream logger
30
+ path: str | None = None
31
+ Path to a file for persistent logging if needed
32
+ level: LogLevel
33
+ The minimum level threshold for messages to appear in your logs
34
+
35
+ Returns
36
+ -------
37
+ Logger
38
+ A configured logging.Logger instance
39
+ """
40
+
41
+ handlers = []
42
+
43
+ # Initialize a logger
44
+ logger = logging.getLogger(name)
45
+ logger.setLevel(level)
46
+
47
+ # 1. Formatter (applied to all handlers)
48
+ fmt = "%(asctime)s [%(levelname)s "
49
+ fmt += "%(name)s(line:%(lineno)d)]: %(message)s"
50
+ formatter = logging.Formatter(fmt=fmt, datefmt="%Y%m%d.%H:%M:%S")
51
+
52
+ # 2. File Handler (for persistent storage)
53
+ if path is not None:
54
+ file_handler = logging.handlers.RotatingFileHandler(
55
+ path,
56
+ maxBytes=1024 * 1024,
57
+ backupCount=5,
58
+ )
59
+ file_handler.setLevel(level)
60
+ file_handler.setFormatter(formatter)
61
+ handlers.append(file_handler)
62
+
63
+ # 3. Console Handler (for real-time feedback)
64
+ if console:
65
+ console_handler = logging.StreamHandler()
66
+ console_handler.setLevel(level)
67
+ console_handler.setFormatter(formatter)
68
+ handlers.append(console_handler)
69
+
70
+ # 4. Add handlers (if they are not already present)
71
+ if not logger.handlers:
72
+ for handler in handlers:
73
+ logger.addHandler(handler)
74
+
75
+ return logger
File without changes