basebender 0.2.1__py3-none-any.whl

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,13 @@
1
+ # `src/` Directory Structure
2
+
3
+ This directory contains the core source code for the application.
4
+
5
+ ## Folders:
6
+
7
+ * [`api/`](src/api/STRUCTURE.md): Contains the FastAPI application for the project's API.
8
+ * [`gui/`](src/gui/STRUCTURE.md): Contains the graphical user interface components.
9
+ * [`rebaser/`](src/rebaser/STRUCTURE.md): Contains the core rebase logic and related modules.
10
+
11
+ ## Files:
12
+
13
+ * [`cli.py`](src/cli.py): Implements the command-line interface for the application.
basebender/__init__.py ADDED
@@ -0,0 +1,8 @@
1
+ """Basebender: A Python package. Refer to the documentation for more details."""
2
+
3
+ try:
4
+ from importlib.metadata import PackageNotFoundError, version
5
+
6
+ VERSION = version("basebender") # <--- Replace "basebender" with your actual package name
7
+ except PackageNotFoundError, ImportError:
8
+ VERSION = "0.0.0+unknown"
@@ -0,0 +1,7 @@
1
+ # `src/api/` Directory Structure
2
+
3
+ This directory contains the FastAPI application for the project's API.
4
+
5
+ ## Files:
6
+
7
+ * [`main.py`](src/api/main.py): The main entry point for the FastAPI application.
basebender/api/main.py ADDED
@@ -0,0 +1,249 @@
1
+ """
2
+ This module defines the FastAPI application for the BaseBender API.
3
+
4
+ It provides endpoints for listing available digit sets and rebaseing text
5
+ between different digit sets.
6
+ """
7
+
8
+ import functools
9
+
10
+ import uvicorn
11
+ from fastapi import FastAPI, HTTPException
12
+ from fastapi.responses import RedirectResponse
13
+ from pydantic import BaseModel
14
+
15
+ from basebender.rebaser.digit_set_rebaser import DigitSetRebaser
16
+ from basebender.rebaser.digit_sets import get_predefined_digit_sets
17
+ from basebender.rebaser.models import DigitSet
18
+
19
+ APP = FastAPI(
20
+ title="BaseBender API",
21
+ description=(
22
+ "API for rebaseing text between different digit sets and listing available digit sets."
23
+ ),
24
+ version="1.0.0",
25
+ )
26
+
27
+
28
+ @functools.cache
29
+ def _load_digit_set_data() -> dict[str, DigitSet]:
30
+ """
31
+ Loads and caches predefined digit set data.
32
+
33
+ Returns:
34
+ A dictionary mapping digit set IDs to DigitSet objects.
35
+ """
36
+ return get_predefined_digit_sets()
37
+
38
+
39
+ @APP.get("/", include_in_schema=False)
40
+ async def redirect_to_docs() -> RedirectResponse:
41
+ """
42
+ Redirects the root URL ("/") to the API documentation page ("/docs").
43
+
44
+ Returns:
45
+ A RedirectResponse to the /docs endpoint.
46
+ """
47
+ return RedirectResponse(url="/docs")
48
+
49
+
50
+ class DigitSetInfo(BaseModel):
51
+ """
52
+ Pydantic model representing information about a digit set.
53
+
54
+ Attributes:
55
+ id: The unique identifier of the digit set (e.g., "binary", "decimal").
56
+ name: A human-readable name for the digit set (e.g., "Binary", "Decimal").
57
+ digits: The string containing all unique digits of the set in order.
58
+ source: The origin of the digit set (e.g., "predefined", "cli_input", "api_input").
59
+ """
60
+
61
+ id: str
62
+ name: str
63
+ digits: str
64
+ source: str
65
+
66
+
67
+ @APP.get(
68
+ "/digitsets",
69
+ response_model=list[DigitSetInfo],
70
+ summary="List all available digit sets",
71
+ )
72
+ async def list_digit_sets() -> list[DigitSetInfo]:
73
+ """
74
+ Retrieves a list of all available digit sets, including their unique IDs,
75
+ names, digits, and source.
76
+ """
77
+ digit_sets = _load_digit_set_data()
78
+ digit_set_list: list[DigitSetInfo] = []
79
+ for digit_set_id, digit_set_info in digit_sets.items():
80
+ digit_set_list.append(
81
+ DigitSetInfo(
82
+ id=digit_set_id,
83
+ name=digit_set_info.name,
84
+ digits=digit_set_info.digits,
85
+ source=digit_set_info.source,
86
+ )
87
+ )
88
+ return digit_set_list
89
+
90
+
91
+ class RebaseRequest(BaseModel):
92
+ """
93
+ Pydantic model for a rebase operation request.
94
+
95
+ Attributes:
96
+ input_text: The string to be rebased. Defaults to an empty string.
97
+ source_digit_set: An optional string representing the source digit set
98
+ (e.g., "0123456789ABCDEF"). If provided, takes precedence over `source_digit_set_id`.
99
+ source_digit_set_id: An optional ID of a predefined source digit set.
100
+ target_digit_set: An optional string representing the target digit set.
101
+ If provided, takes precedence over `target_digit_set_id`.
102
+ target_digit_set_id: An optional ID of a predefined target digit set.
103
+ """
104
+
105
+ input_text: str | None = ""
106
+ source_digit_set: str | None = None
107
+ source_digit_set_id: str | None = None
108
+ target_digit_set: str | None = None
109
+ target_digit_set_id: str | None = None
110
+
111
+
112
+ class ErrorResponse(BaseModel):
113
+ """
114
+ Pydantic model for standard API error responses.
115
+
116
+ Attributes:
117
+ message: A concise summary of the error.
118
+ detail: An optional, more detailed explanation of the error.
119
+ """
120
+
121
+ message: str
122
+ detail: str | None = None
123
+
124
+
125
+ class RebaseResponse(BaseModel):
126
+ """
127
+ Pydantic model for the response of a rebase operation.
128
+
129
+ Attributes:
130
+ rebased_text: The string after the rebase operation.
131
+ source_digit_set_used: A string indicating the source digit set that was used
132
+ (e.g., "Binary", "Provided: '012'").
133
+ target_digit_set_used: A string indicating the target digit set that was used
134
+ (e.g., "Decimal", "Provided: 'abc'").
135
+ error: An optional `ErrorResponse` object if an error occurred during rebase.
136
+ """
137
+
138
+ rebased_text: str
139
+ source_digit_set_used: str
140
+ target_digit_set_used: str
141
+ error: ErrorResponse | None = None
142
+
143
+
144
+ @APP.post(
145
+ "/rebase",
146
+ response_model=RebaseResponse,
147
+ summary="Rebase text between digit sets",
148
+ )
149
+ async def rebase_text(request: RebaseRequest) -> RebaseResponse:
150
+ """
151
+ Rebasees input text from a source digit set to a target digit set.
152
+
153
+ - If `input_text` is not provided, an empty string is used.
154
+ - If `source_digit_set_id` is not provided, the source digit set is
155
+ dynamically derived from `input_text`.
156
+ - If `target_digit_set_id` is not provided, the input string is returned
157
+ with digits not in the derived/provided source digit set removed. If the
158
+ target digit set has a length of 1, an empty string will be returned.
159
+ """
160
+ digit_sets_data = _load_digit_set_data()
161
+
162
+ input_text: str = request.input_text if request.input_text is not None else ""
163
+ source_digit_set_obj: DigitSet | None = None
164
+ target_digit_set_obj: DigitSet | None = None
165
+ source_digit_set_name: str = "Dynamically Derived"
166
+ target_digit_set_name: str = "Echo Input"
167
+
168
+ # Determine source digit set
169
+ if request.source_digit_set: # Direct digit set string takes precedence
170
+ source_digit_set_obj = DigitSet(
171
+ name="Provided", digits=request.source_digit_set, source="api_input"
172
+ )
173
+ source_digit_set_name = f"Provided: '{request.source_digit_set}'"
174
+ elif request.source_digit_set_id:
175
+ source_digit_set_obj = digit_sets_data.get(request.source_digit_set_id)
176
+ if source_digit_set_obj is None:
177
+ raise HTTPException(
178
+ status_code=400,
179
+ detail=ErrorResponse(
180
+ message="Invalid Source Digit Set ID",
181
+ detail=(
182
+ f"Source digit set with ID '{request.source_digit_set_id}' not found."
183
+ ),
184
+ ).model_dump(),
185
+ ) from None
186
+ source_digit_set_name = source_digit_set_obj.name
187
+
188
+ # Determine target digit set
189
+ if request.target_digit_set: # Direct digit set string takes precedence
190
+ target_digit_set_obj = DigitSet(
191
+ name="Provided", digits=request.target_digit_set, source="api_input"
192
+ )
193
+ target_digit_set_name = f"Provided: '{request.target_digit_set}'"
194
+ elif request.target_digit_set_id:
195
+ target_digit_set_obj = digit_sets_data.get(request.target_digit_set_id)
196
+ if target_digit_set_obj is None:
197
+ raise HTTPException(
198
+ status_code=400,
199
+ detail=ErrorResponse(
200
+ message="Invalid Target Digit Set ID",
201
+ detail=(
202
+ f"Target digit set with ID '{request.target_digit_set_id}' not found."
203
+ ),
204
+ ).model_dump(),
205
+ ) from None
206
+ target_digit_set_name = target_digit_set_obj.name
207
+
208
+ rebased_text: str = ""
209
+ error_response: ErrorResponse | None = None
210
+
211
+ try:
212
+ rebaser = DigitSetRebaser(
213
+ out_digit_set=target_digit_set_obj,
214
+ in_digit_set=source_digit_set_obj,
215
+ )
216
+ rebased_text = rebaser.rebase(input_text)
217
+ except ValueError as exc:
218
+ error_response = ErrorResponse(
219
+ message="Rebase Error",
220
+ detail=f"A value error occurred during rebase: {exc}",
221
+ )
222
+ raise HTTPException(status_code=400, detail=error_response.model_dump()) from exc
223
+ except IndexError as exc:
224
+ error_response = ErrorResponse(
225
+ message="Rebase Error",
226
+ detail=f"An index error occurred during rebase: {exc}",
227
+ )
228
+ raise HTTPException(status_code=400, detail=error_response.model_dump()) from exc
229
+ except Exception as exc: # pylint: disable=broad-exception-caught
230
+ # Catching broad exception as an isolation point for unexpected errors.
231
+ error_response = ErrorResponse(
232
+ message="Internal Server Error",
233
+ detail=f"An unexpected error occurred: {exc}",
234
+ )
235
+ raise HTTPException(status_code=500, detail=error_response.model_dump()) from exc
236
+
237
+ return RebaseResponse(
238
+ rebased_text=rebased_text,
239
+ source_digit_set_used=source_digit_set_name,
240
+ target_digit_set_used=target_digit_set_name,
241
+ error=error_response,
242
+ )
243
+
244
+
245
+ def start_api() -> None:
246
+ """
247
+ Starts the FastAPI server using uvicorn.
248
+ """
249
+ uvicorn.run("basebender.api.main:APP", host="0.0.0.0", port=8000, reload=True)
basebender/cli.py ADDED
@@ -0,0 +1,231 @@
1
+ """
2
+ This module provides a command-line interface (CLI) for the BaseBender tool.
3
+
4
+ It allows users to rebase strings between different digit sets, list available
5
+ digit sets, suggest digit sets for a given string, and launch a graphical user
6
+ interface or an API server.
7
+ """
8
+
9
+ import argparse
10
+ import logging
11
+ import sys
12
+
13
+ import uvicorn
14
+
15
+ from basebender.rebaser.digit_set_rebaser import DigitSetRebaser
16
+ from basebender.rebaser.digit_sets import (
17
+ get_predefined_digit_sets,
18
+ suggest_digit_sets,
19
+ )
20
+ from basebender.rebaser.models import DigitSet
21
+
22
+
23
+ def list_digit_sets_cli() -> int:
24
+ """
25
+ Lists all available pre-defined digit sets to the console.
26
+
27
+ Returns:
28
+ An exit code (0 for success).
29
+ """
30
+ print("Pre-defined Digit Sets:")
31
+ digit_sets = get_predefined_digit_sets()
32
+ for digit_set_id, digit_set_info in digit_sets.items():
33
+ print(
34
+ f" {digit_set_id} (Name: {digit_set_info.name}, "
35
+ f"Source: {digit_set_info.source}): {digit_set_info.digits}"
36
+ )
37
+ return 0
38
+
39
+
40
+ def suggest_digit_sets_cli(input_string: str) -> int:
41
+ """
42
+ Suggests pre-defined digit sets based on characters in the input string.
43
+
44
+ Args:
45
+ input_string: The string for which to suggest digit sets.
46
+
47
+ Returns:
48
+ An exit code (0 for success).
49
+ """
50
+ suggestions = suggest_digit_sets(input_string)
51
+ if suggestions:
52
+ print(f"Suggested Digit Sets for '{input_string}':")
53
+ for suggestion in suggestions:
54
+ print(f" - {suggestion}")
55
+ else:
56
+ print(f"No pre-defined digit sets suggested for '{input_string}'.")
57
+ return 0
58
+
59
+
60
+ def perform_rebase_cli(
61
+ input_string: str | None,
62
+ output_digit_set_str: str | None,
63
+ input_digit_set_str: str | None,
64
+ ) -> int:
65
+ """
66
+ Performs the digit set rebase operation and prints the result to the console.
67
+
68
+ This function handles the dynamic derivation of input and output digit sets
69
+ from predefined sets or user-provided strings. It also includes error
70
+ reporting for invalid operations.
71
+
72
+ Args:
73
+ input_string: The string to be rebased.
74
+ output_digit_set_str: The identifier or string representation of the
75
+ target digit set.
76
+ input_digit_set_str: The identifier or string representation of the
77
+ source digit set.
78
+
79
+ Returns:
80
+ An exit code (0 for success, 1 for error).
81
+ """
82
+ if not input_string:
83
+ print("Rebased string: ")
84
+ return 0
85
+
86
+ digit_sets_data = get_predefined_digit_sets()
87
+ input_digit_set_obj: DigitSet | None = None
88
+ output_digit_set_obj: DigitSet | None = None
89
+
90
+ # Determine input digit set
91
+ if input_digit_set_str:
92
+ if input_digit_set_str in digit_sets_data:
93
+ input_digit_set_obj = digit_sets_data[input_digit_set_str]
94
+ else:
95
+ input_digit_set_obj = DigitSet(
96
+ name="Provided", digits=input_digit_set_str, source="cli_input"
97
+ )
98
+
99
+ # Determine output digit set
100
+ if output_digit_set_str:
101
+ if output_digit_set_str in digit_sets_data:
102
+ output_digit_set_obj = digit_sets_data[output_digit_set_str]
103
+ else:
104
+ output_digit_set_obj = DigitSet(
105
+ name="Provided", digits=output_digit_set_str, source="cli_input"
106
+ )
107
+
108
+ try:
109
+ rebaser = DigitSetRebaser(
110
+ out_digit_set=output_digit_set_obj, in_digit_set=input_digit_set_obj
111
+ )
112
+ rebased_string = rebaser.rebase(input_string)
113
+ print(f"Rebased string: {rebased_string}")
114
+ return 0
115
+ except ValueError as exc:
116
+ logging.error("Error: Invalid digit set or rebase operation. %s", exc)
117
+ return 1
118
+ except IndexError as exc:
119
+ logging.error(
120
+ "Error: Digit set index out of bounds. This usually indicates an "
121
+ "internal issue with digit set mapping. %s",
122
+ exc,
123
+ )
124
+ return 1
125
+ except Exception as exc: # pylint: disable=broad-exception-caught
126
+ # Catching broad exception as an isolation point for unexpected errors.
127
+ logging.error("An unexpected error occurred during rebase: %s", exc)
128
+ return 1
129
+
130
+
131
+ def main() -> None:
132
+ """
133
+ Main entry point for the BaseBender command-line interface.
134
+
135
+ This function parses command-line arguments, handles various CLI operations
136
+ like listing digit sets, suggesting digit sets, launching the GUI or API,
137
+ and performing string rebase operations.
138
+ """
139
+ parser = argparse.ArgumentParser(
140
+ description=(
141
+ "BaseBender: A tool for rebaseing strings between different "
142
+ "digit sets (positional number systems)."
143
+ ),
144
+ epilog="For more information, refer to the project's README.md.",
145
+ )
146
+ parser.add_argument(
147
+ "input_string",
148
+ nargs="?",
149
+ default=None,
150
+ help=(
151
+ "The string to be rebased. Required unless --list-digit-sets, "
152
+ "--suggest-digit-sets, --gui, or --api is used."
153
+ ),
154
+ )
155
+ parser.add_argument(
156
+ "output_digit_set",
157
+ nargs="?",
158
+ default=None,
159
+ help=(
160
+ "The target digit set string for the rebase operation. If "
161
+ "omitted, the input string will be filtered based on the input "
162
+ "digit set."
163
+ ),
164
+ )
165
+ parser.add_argument(
166
+ "input_digit_set",
167
+ nargs="?",
168
+ default=None,
169
+ help=(
170
+ "The source digit set string for the rebase operation. If "
171
+ "omitted, the input digit set will be dynamically derived from "
172
+ "the input string."
173
+ ),
174
+ )
175
+ parser.add_argument(
176
+ "-l",
177
+ "--list-digit-sets",
178
+ action="store_true",
179
+ help=("List all available pre-defined digit sets loaded from configuration files."),
180
+ )
181
+ parser.add_argument(
182
+ "-s",
183
+ "--suggest-digit-sets",
184
+ metavar="STRING",
185
+ help=(
186
+ "Suggest pre-defined digit sets that are relevant to the "
187
+ "provided STRING based on its characters."
188
+ ),
189
+ )
190
+ parser.add_argument(
191
+ "--gui",
192
+ action="store_true",
193
+ help="Launch the graphical user interface for interactive rebaseing.",
194
+ )
195
+ parser.add_argument(
196
+ "--api",
197
+ action="store_true",
198
+ help=("Start the FastAPI server, providing a web API for rebase operations."),
199
+ )
200
+
201
+ args = parser.parse_args()
202
+ exit_code = 0
203
+
204
+ if args.gui:
205
+ from basebender.gui.main_window import run_gui
206
+
207
+ run_gui()
208
+ elif args.api:
209
+ # Ensure the API module is importable from the current directory
210
+ uvicorn.run("basebender.api.main:APP", host="0.0.0.0", port=8000, reload=True)
211
+ elif args.list_digit_sets:
212
+ exit_code = list_digit_sets_cli()
213
+ elif args.suggest_digit_sets:
214
+ exit_code = suggest_digit_sets_cli(args.suggest_digit_sets)
215
+ else:
216
+ # If no specific action (list, suggest, gui, api) is requested, rebase
217
+ if args.input_string is None and not (
218
+ args.list_digit_sets or args.suggest_digit_sets or args.gui or args.api
219
+ ):
220
+ parser.error(
221
+ "input_string is required for rebase when not using "
222
+ "--list-digit-sets, --suggest-digit-sets, --gui, or --api."
223
+ )
224
+ exit_code = perform_rebase_cli(
225
+ args.input_string, args.output_digit_set, args.input_digit_set
226
+ )
227
+ sys.exit(exit_code)
228
+
229
+
230
+ if __name__ == "__main__":
231
+ main()
@@ -0,0 +1,8 @@
1
+ # `src/gui/` Directory Structure
2
+
3
+ This directory contains the graphical user interface components.
4
+
5
+ ## Files:
6
+
7
+ * [`__init__.py`](src/gui/__init__.py): Initializes the `gui` package.
8
+ * [`main_window.py`](src/gui/main_window.py): Defines the main window of the GUI application.
@@ -0,0 +1,3 @@
1
+ """
2
+ This package provides the graphical user interface for the BaseBender tool.
3
+ """