tiferet 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.
- tiferet-1.0.0/LICENSE +28 -0
- tiferet-1.0.0/PKG-INFO +25 -0
- tiferet-1.0.0/README.md +620 -0
- tiferet-1.0.0/setup.cfg +4 -0
- tiferet-1.0.0/setup.py +39 -0
- tiferet-1.0.0/tiferet/__init__.py +6 -0
- tiferet-1.0.0/tiferet/clients/__init__.py +1 -0
- tiferet-1.0.0/tiferet/clients/yaml.py +102 -0
- tiferet-1.0.0/tiferet/commands/__init__.py +6 -0
- tiferet-1.0.0/tiferet/commands/app.py +102 -0
- tiferet-1.0.0/tiferet/commands/core.py +124 -0
- tiferet-1.0.0/tiferet/commands/dependencies.py +76 -0
- tiferet-1.0.0/tiferet/commands/settings.py +101 -0
- tiferet-1.0.0/tiferet/configs/__init__.py +5 -0
- tiferet-1.0.0/tiferet/configs/app.py +59 -0
- tiferet-1.0.0/tiferet/configs/error.py +152 -0
- tiferet-1.0.0/tiferet/configs/settings.py +37 -0
- tiferet-1.0.0/tiferet/contexts/__init__.py +0 -0
- tiferet-1.0.0/tiferet/contexts/app.py +281 -0
- tiferet-1.0.0/tiferet/contexts/cache.py +76 -0
- tiferet-1.0.0/tiferet/contexts/container.py +125 -0
- tiferet-1.0.0/tiferet/contexts/error.py +124 -0
- tiferet-1.0.0/tiferet/contexts/feature.py +167 -0
- tiferet-1.0.0/tiferet/contracts/__init__.py +4 -0
- tiferet-1.0.0/tiferet/contracts/app.py +141 -0
- tiferet-1.0.0/tiferet/contracts/cache.py +70 -0
- tiferet-1.0.0/tiferet/contracts/container.py +147 -0
- tiferet-1.0.0/tiferet/contracts/error.py +177 -0
- tiferet-1.0.0/tiferet/contracts/feature.py +159 -0
- tiferet-1.0.0/tiferet/contracts/settings.py +34 -0
- tiferet-1.0.0/tiferet/data/__init__.py +4 -0
- tiferet-1.0.0/tiferet/data/app.py +148 -0
- tiferet-1.0.0/tiferet/data/container.py +227 -0
- tiferet-1.0.0/tiferet/data/error.py +89 -0
- tiferet-1.0.0/tiferet/data/feature.py +127 -0
- tiferet-1.0.0/tiferet/data/settings.py +159 -0
- tiferet-1.0.0/tiferet/handlers/__init__.py +0 -0
- tiferet-1.0.0/tiferet/handlers/app.py +120 -0
- tiferet-1.0.0/tiferet/handlers/container.py +119 -0
- tiferet-1.0.0/tiferet/handlers/error.py +49 -0
- tiferet-1.0.0/tiferet/handlers/feature.py +94 -0
- tiferet-1.0.0/tiferet/models/__init__.py +4 -0
- tiferet-1.0.0/tiferet/models/app.py +153 -0
- tiferet-1.0.0/tiferet/models/container.py +135 -0
- tiferet-1.0.0/tiferet/models/error.py +204 -0
- tiferet-1.0.0/tiferet/models/feature.py +244 -0
- tiferet-1.0.0/tiferet/models/settings.py +148 -0
- tiferet-1.0.0/tiferet/proxies/__init__.py +0 -0
- tiferet-1.0.0/tiferet/proxies/yaml/__init__.py +4 -0
- tiferet-1.0.0/tiferet/proxies/yaml/app.py +107 -0
- tiferet-1.0.0/tiferet/proxies/yaml/container.py +119 -0
- tiferet-1.0.0/tiferet/proxies/yaml/core.py +58 -0
- tiferet-1.0.0/tiferet/proxies/yaml/error.py +138 -0
- tiferet-1.0.0/tiferet/proxies/yaml/feature.py +119 -0
- tiferet-1.0.0/tiferet.egg-info/PKG-INFO +25 -0
- tiferet-1.0.0/tiferet.egg-info/SOURCES.txt +57 -0
- tiferet-1.0.0/tiferet.egg-info/dependency_links.txt +1 -0
- tiferet-1.0.0/tiferet.egg-info/requires.txt +7 -0
- tiferet-1.0.0/tiferet.egg-info/top_level.txt +1 -0
tiferet-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024, Great Strength Systems LLC
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
16
|
+
contributors may be used to endorse or promote products derived from
|
|
17
|
+
this software without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
tiferet-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tiferet
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: A multi-purpose application framework embodying beauty in form.
|
|
5
|
+
Home-page: https://github.com/greatstrength/app
|
|
6
|
+
Download-URL: https://github.com/greatstrength/app
|
|
7
|
+
Author: Andrew Shatz, Great Strength Systems
|
|
8
|
+
Author-email: andrew@greatstrength.me
|
|
9
|
+
License: BSD 3
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Requires-Dist: schematics>=2.1.1
|
|
12
|
+
Requires-Dist: pyyaml>=6.0.1
|
|
13
|
+
Requires-Dist: dependencies>=7.7.0
|
|
14
|
+
Provides-Extra: test
|
|
15
|
+
Requires-Dist: pytest>=8.3.3; extra == "test"
|
|
16
|
+
Requires-Dist: pytest_env>=1.1.5; extra == "test"
|
|
17
|
+
Dynamic: author
|
|
18
|
+
Dynamic: author-email
|
|
19
|
+
Dynamic: download-url
|
|
20
|
+
Dynamic: home-page
|
|
21
|
+
Dynamic: license
|
|
22
|
+
Dynamic: license-file
|
|
23
|
+
Dynamic: provides-extra
|
|
24
|
+
Dynamic: requires-dist
|
|
25
|
+
Dynamic: summary
|
tiferet-1.0.0/README.md
ADDED
|
@@ -0,0 +1,620 @@
|
|
|
1
|
+
# Tiferet - A Python Framework for Domain-Driven Design
|
|
2
|
+
|
|
3
|
+
## Introduction
|
|
4
|
+
|
|
5
|
+
Tiferet is a Python framework that elegantly distills Domain-Driven Design (DDD) into a practical, powerful tool. Drawing inspiration from the Kabbalistic concept of beauty in balance, Tiferet weaves purpose and functionality into software that not only performs but resonates deeply with its intended vision. As a cornerstone for crafting diverse applications, Tiferet empowers developers to build solutions with clarity, grace, and thoughtful design.
|
|
6
|
+
|
|
7
|
+
Tiferet embraces the complexity of real-world processes through DDD, transforming intricate business logic and evolving requirements into clear, manageable models. Far from merely navigating this labyrinth, Tiferet provides a graceful path to craft software that reflects its intended purpose with wisdom and precision, embodying the Kabbalistic beauty of balanced form and function. This tutorial guides you through building a simple calculator application, demonstrating how Tiferet harmonizes code and concept. By defining a domain model, command classes, and feature-driven configurations, you’ll create a robust and extensible calculator that resonates with Tiferet’s philosophy.
|
|
8
|
+
|
|
9
|
+
## Getting Started with Tiferet
|
|
10
|
+
Embark on your Tiferet journey with a few simple steps to set up your Python environment. Whether you're new to Python or a seasoned developer, these instructions will prepare you to craft a calculator application with grace and precision.
|
|
11
|
+
|
|
12
|
+
### Installing Python
|
|
13
|
+
Tiferet requires Python 3.10 or later. Follow these steps to install it:
|
|
14
|
+
|
|
15
|
+
#### Windows
|
|
16
|
+
|
|
17
|
+
Visit python.org, navigate to the Downloads section, and select the Python 3.10 installer for Windows.
|
|
18
|
+
Run the installer, ensuring you check "Add Python 3.10 to PATH," then click "Install Now."
|
|
19
|
+
|
|
20
|
+
#### macOS
|
|
21
|
+
|
|
22
|
+
Download the Python 3.10 installer from python.org.
|
|
23
|
+
Open the .pkg file and follow the installation prompts.
|
|
24
|
+
|
|
25
|
+
#### Linux (Ubuntu/Debian)
|
|
26
|
+
```bash
|
|
27
|
+
sudo apt update
|
|
28
|
+
sudo apt install software-properties-common
|
|
29
|
+
sudo add-apt-repository ppa:deadsnakes/ppa
|
|
30
|
+
sudo apt update
|
|
31
|
+
sudo apt install python3.10
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
#### Verify the installation by running:
|
|
35
|
+
```bash
|
|
36
|
+
python3.10 --version
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
You should see Python 3.10.x if successful.
|
|
40
|
+
|
|
41
|
+
### Setting Up a Virtual Environment
|
|
42
|
+
To keep your project dependencies organized, create a virtual environment named `tiferet_app` for your calculator application:
|
|
43
|
+
|
|
44
|
+
#### Create the Environment
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
# Windows
|
|
48
|
+
python -m venv tiferet_app
|
|
49
|
+
|
|
50
|
+
# macOS/Linux
|
|
51
|
+
python3.10 -m venv tiferet_app
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
#### Activate the Environment
|
|
55
|
+
Activate the environment to isolate your project's dependencies:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
# Windows (Command Prompt)
|
|
59
|
+
tiferet_app\Scripts\activate
|
|
60
|
+
|
|
61
|
+
# Windows (PowerShell)
|
|
62
|
+
.\tiferet_app\Scripts\Activate.ps1
|
|
63
|
+
|
|
64
|
+
# macOS/Linux
|
|
65
|
+
source tiferet_app/bin/activate
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Your terminal should display `(tiferet_app)`, confirming the environment is active. You can now install Tiferet and other dependencies without affecting your system’s Python setup.
|
|
69
|
+
Deactivate the Environment
|
|
70
|
+
When finished, deactivate the environment with:
|
|
71
|
+
deactivate
|
|
72
|
+
|
|
73
|
+
## Your First Calculator App
|
|
74
|
+
With your `tiferet_app` virtual environment activated, you're ready to install Tiferet and start building your calculator application. Follow these steps to set up your project and begin crafting with Tiferet’s elegant approach.
|
|
75
|
+
|
|
76
|
+
### Installing Tiferet
|
|
77
|
+
Install the Tiferet package using pip in your activated virtual environment:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
# Windows
|
|
81
|
+
pip install tiferet
|
|
82
|
+
|
|
83
|
+
# macOS/Linux
|
|
84
|
+
pip3 install tiferet
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Project Structure
|
|
88
|
+
Create a project directory structure to organize your calculator application:
|
|
89
|
+
|
|
90
|
+
```plaintext
|
|
91
|
+
project_root/
|
|
92
|
+
├── basic_calc.py
|
|
93
|
+
├── calc_cli.py
|
|
94
|
+
└── app/
|
|
95
|
+
├── commands/
|
|
96
|
+
│ ├── __init__.py
|
|
97
|
+
│ ├── calc.py
|
|
98
|
+
│ └── valid.py
|
|
99
|
+
├── configs/
|
|
100
|
+
│ ├── __init__.py
|
|
101
|
+
│ └── config.yml
|
|
102
|
+
└── models/
|
|
103
|
+
├── __init__.py
|
|
104
|
+
└── calc.py
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
The `app/models/` directory will house the calculator’s domain model, `app/commands/` will contain command classes for operations and validations, and `app/configs/` will store configuration files. The `basic_calc.py` script at the root will initialize and run the application. While the app directory name is customizable for package releases, we recommend retaining it for internal or proprietary projects to maintain simplicity and consistency. The `calc_cli.py` script is a versatile scriptable interface that integrates easily with shell scripts or external systems.
|
|
108
|
+
|
|
109
|
+
## Crafting the Calculator Application
|
|
110
|
+
With Tiferet installed and your project structured, it’s time to bring your calculator application to life. We’ll start by defining the domain model, then create command classes for arithmetic and validation, configure the application’s behavior through container attributes, features, errors, and context, and finally initialize and demonstrate the app with a script. This sequence showcases Tiferet’s harmonious design, weaving together models, commands, and configurations with grace.
|
|
111
|
+
|
|
112
|
+
### Defining the Number Model in `models/calc.py`
|
|
113
|
+
The calculator’s numerical values are represented by a `Number` value object, defined in `app/models/calc.py`. This model encapsulates a string-based numerical value, validated to ensure it represents an integer or float, and provides a method to format it as a number.
|
|
114
|
+
|
|
115
|
+
Create `app/models/calc.py` with the following content:
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
from tiferet.models import *
|
|
119
|
+
|
|
120
|
+
class Number(ValueObject):
|
|
121
|
+
'''
|
|
122
|
+
A value object representing a numerical value in the calculator domain.
|
|
123
|
+
'''
|
|
124
|
+
value = StringType(
|
|
125
|
+
required=True,
|
|
126
|
+
regex=r'^-?\d*\.?\d*$',
|
|
127
|
+
metadata=dict(
|
|
128
|
+
description='A string representing an integer or float (e.g., "123", "-123.45", "0123", ".123", "123.").'
|
|
129
|
+
)
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
def is_float(self) -> bool:
|
|
133
|
+
'''
|
|
134
|
+
Check if the value is formatted as a float.
|
|
135
|
+
|
|
136
|
+
:return: True if the value contains a decimal point and valid digits, False otherwise.
|
|
137
|
+
'''
|
|
138
|
+
return '.' in self.value and self.value.strip('-.').replace('.', '').isdigit()
|
|
139
|
+
|
|
140
|
+
def format(self) -> int | float:
|
|
141
|
+
'''
|
|
142
|
+
Convert the string value to an integer or float.
|
|
143
|
+
|
|
144
|
+
:return: An integer if the value represents a whole number, otherwise a float.
|
|
145
|
+
'''
|
|
146
|
+
if self.is_float():
|
|
147
|
+
return float(self.value)
|
|
148
|
+
return int(self.value)
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
The Number class uses Tiferet’s ValueObject to ensure immutability, with a `StringType` attribute validated by a regex to accept integers and floats (e.g., `"123"`, `"-123.45"`, `".123"`). The is_float method checks for decimal points, and format converts the string to an int or float, enabling arithmetic operations.
|
|
152
|
+
|
|
153
|
+
### Defining Command Classes in commands/calc.py and commands/valid.py
|
|
154
|
+
Next, we define command classes to perform arithmetic operations and input validation. Arithmetic commands (`AddNumber`, `SubtractNumber`, `MultiplyNumber`, `DivideNumber`, `ExponentiateNumber`) are in `app/commands/calc.py`, while the validation command (`ValidateNumber`) is in `app/commands/valid.py`. All inherit from Tiferet’s Command base class, using the static `Command.handle` method for execution.
|
|
155
|
+
|
|
156
|
+
#### Arithmetic Commands in commands/calc.py
|
|
157
|
+
Create `app/commands/calc.py` with the following content:
|
|
158
|
+
|
|
159
|
+
```python
|
|
160
|
+
from tiferet.commands import *
|
|
161
|
+
|
|
162
|
+
from ..models.calc import Number
|
|
163
|
+
|
|
164
|
+
class AddNumber(Command):
|
|
165
|
+
'''
|
|
166
|
+
A command to perform addition of two numbers.
|
|
167
|
+
'''
|
|
168
|
+
def execute(self, a: Number, b: Number, **kwargs) -> Number:
|
|
169
|
+
'''
|
|
170
|
+
Execute the addition command.
|
|
171
|
+
|
|
172
|
+
:param a: A Number object representing the first number.
|
|
173
|
+
:type a: Number
|
|
174
|
+
:param b: A Number object representing the second number.
|
|
175
|
+
:type b: Number
|
|
176
|
+
:param kwargs: Additional keyword arguments.
|
|
177
|
+
:type kwargs: dict
|
|
178
|
+
:return: A Number object representing the sum of a and b.
|
|
179
|
+
:rtype: Number
|
|
180
|
+
'''
|
|
181
|
+
|
|
182
|
+
# Add formatted values of a and b.
|
|
183
|
+
result = a.format() + b.format()
|
|
184
|
+
|
|
185
|
+
# Return a new Number object with the result.
|
|
186
|
+
return result
|
|
187
|
+
|
|
188
|
+
class SubtractNumber(Command):
|
|
189
|
+
'''
|
|
190
|
+
A command to perform subtraction of two numbers.
|
|
191
|
+
'''
|
|
192
|
+
def execute(self, a: Number, b: Number, **kwargs) -> Number:
|
|
193
|
+
'''
|
|
194
|
+
Execute the subtraction command.
|
|
195
|
+
|
|
196
|
+
:param a: A Number object representing the first number.
|
|
197
|
+
:type a: Number
|
|
198
|
+
:param b: A Number object representing the second number.
|
|
199
|
+
:type b: Number
|
|
200
|
+
:param kwargs: Additional keyword arguments.
|
|
201
|
+
:type kwargs: dict
|
|
202
|
+
:return: A Number object representing the difference of a and b.
|
|
203
|
+
:rtype: Number
|
|
204
|
+
'''
|
|
205
|
+
|
|
206
|
+
# Subtract formatted values of b from a.
|
|
207
|
+
result = a.format() - b.format()
|
|
208
|
+
|
|
209
|
+
# Return a new Number object with the result.
|
|
210
|
+
return result
|
|
211
|
+
|
|
212
|
+
class MultiplyNumber(Command):
|
|
213
|
+
'''
|
|
214
|
+
A command to perform multiplication of two numbers.
|
|
215
|
+
'''
|
|
216
|
+
def execute(self, a: Number, b: Number, **kwargs) -> Number:
|
|
217
|
+
'''
|
|
218
|
+
Execute the multiplication command.
|
|
219
|
+
|
|
220
|
+
:param a: A Number object representing the first number.
|
|
221
|
+
:type a: Number
|
|
222
|
+
:param b: A Number object representing the second number.
|
|
223
|
+
:type b: Number
|
|
224
|
+
:param kwargs: Additional keyword arguments.
|
|
225
|
+
:type kwargs: dict
|
|
226
|
+
:return: A Number object representing the product of a and b.
|
|
227
|
+
:rtype: Number
|
|
228
|
+
'''
|
|
229
|
+
|
|
230
|
+
# Multiply the formatted values of a and b.
|
|
231
|
+
result = a.format() * b.format()
|
|
232
|
+
|
|
233
|
+
# Return a new Number object with the result.
|
|
234
|
+
return result
|
|
235
|
+
|
|
236
|
+
class DivideNumber(Command):
|
|
237
|
+
'''
|
|
238
|
+
A command to perform division of two numbers.
|
|
239
|
+
'''
|
|
240
|
+
def execute(self, a: Number, b: Number, **kwargs) -> Number:
|
|
241
|
+
'''
|
|
242
|
+
Execute the division command.
|
|
243
|
+
|
|
244
|
+
:param a: A Number object representing the first number.
|
|
245
|
+
:type a: Number
|
|
246
|
+
:param b: A Number object representing the second number, must be non-zero.
|
|
247
|
+
:type b: Number
|
|
248
|
+
:param kwargs: Additional keyword arguments.
|
|
249
|
+
:type kwargs: dict
|
|
250
|
+
:return: A Number object representing the quotient of a and b.
|
|
251
|
+
:rtype: Number
|
|
252
|
+
'''
|
|
253
|
+
# Check if b is zero to avoid division by zero.
|
|
254
|
+
self.verify(b.format() != 0, 'DIVISION_BY_ZERO')
|
|
255
|
+
|
|
256
|
+
# Divide the formatted values of a by b.
|
|
257
|
+
result = a.format() / b.format()
|
|
258
|
+
|
|
259
|
+
# Return a new Number object with the result.
|
|
260
|
+
return result
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
class ExponentiateNumber(Command):
|
|
264
|
+
'''
|
|
265
|
+
A command to perform exponentiation of two numbers.
|
|
266
|
+
'''
|
|
267
|
+
def execute(self, a: Number, b: Number, **kwargs) -> Number:
|
|
268
|
+
'''
|
|
269
|
+
Execute the exponentiation command.
|
|
270
|
+
|
|
271
|
+
:param a: A Number object representing the base number.
|
|
272
|
+
:type a: Number
|
|
273
|
+
:param b: A Number object representing the exponent.
|
|
274
|
+
:type b: Number
|
|
275
|
+
:param kwargs: Additional keyword arguments.
|
|
276
|
+
:type kwargs: dict
|
|
277
|
+
:return: A Number object representing a raised to the power of b.
|
|
278
|
+
:rtype: Number
|
|
279
|
+
'''
|
|
280
|
+
|
|
281
|
+
# Exponentiate the formatted value of a by b.
|
|
282
|
+
result = a.format() ** b.format()
|
|
283
|
+
|
|
284
|
+
# Return a new Number object with the result.
|
|
285
|
+
return result
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
These commands perform arithmetic operations on `Number` objects, using `format()` to extract numerical values and `ModelObject.new` to return results as new `Number` objects. The `DivideNumber` command includes a verify check to prevent division by zero, referencing a configured error.
|
|
289
|
+
|
|
290
|
+
#### Validation Command in `commands/valid.py`
|
|
291
|
+
Create `app/commands/valid.py` with the following content:
|
|
292
|
+
|
|
293
|
+
```python
|
|
294
|
+
from tiferet.commands import *
|
|
295
|
+
|
|
296
|
+
from ..models.calc import Number
|
|
297
|
+
|
|
298
|
+
class ValidateNumber(Command):
|
|
299
|
+
'''
|
|
300
|
+
A command to validate that a value can be a Number object.
|
|
301
|
+
'''
|
|
302
|
+
def execute(self, value: str, **kwargs) -> None:
|
|
303
|
+
'''
|
|
304
|
+
Validate that the input value can be used to create a Number object.
|
|
305
|
+
|
|
306
|
+
:param value: Any string value to validate.
|
|
307
|
+
:type value: str
|
|
308
|
+
:param kwargs: Additional keyword arguments.
|
|
309
|
+
:type kwargs: dict
|
|
310
|
+
:raises TiferetError: If the value cannot be a Number.
|
|
311
|
+
'''
|
|
312
|
+
try:
|
|
313
|
+
return ModelObject.new(Number, value=str(value))
|
|
314
|
+
except Exception as e:
|
|
315
|
+
self.verify(False, 'INVALID_INPUT', value)
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
The `ValidateNumber` command ensures inputs can be converted to `Number` objects, raising a configured error for invalid values.
|
|
319
|
+
|
|
320
|
+
### Configuring the Application in `configs/config.yml`
|
|
321
|
+
The calculator’s behavior is defined in `app/configs/config.yml`, which configures container attributes, features, errors, and the application context. This centralized configuration enables Tiferet’s dependency injection container to orchestrate commands and features gracefully.
|
|
322
|
+
|
|
323
|
+
Create `app/configs/config.yml` with the following content:
|
|
324
|
+
|
|
325
|
+
```yaml
|
|
326
|
+
attrs:
|
|
327
|
+
add_number_cmd:
|
|
328
|
+
module_path: app.commands.calc
|
|
329
|
+
class_name: AddNumber
|
|
330
|
+
subtract_number_cmd:
|
|
331
|
+
module_path: app.commands.calc
|
|
332
|
+
class_name: SubtractNumber
|
|
333
|
+
multiply_number_cmd:
|
|
334
|
+
module_path: app.commands.calc
|
|
335
|
+
class_name: MultiplyNumber
|
|
336
|
+
divide_number_cmd:
|
|
337
|
+
module_path: app.commands.calc
|
|
338
|
+
class_name: DivideNumber
|
|
339
|
+
exponentiate_number_cmd:
|
|
340
|
+
module_path: app.commands.calc
|
|
341
|
+
class_name: ExponentiateNumber
|
|
342
|
+
validate_number_cmd:
|
|
343
|
+
module_path: app.commands.valid
|
|
344
|
+
class_name: ValidateNumber
|
|
345
|
+
|
|
346
|
+
features:
|
|
347
|
+
calc.add:
|
|
348
|
+
name: 'Add Number'
|
|
349
|
+
description: 'Adds one number to another'
|
|
350
|
+
commands:
|
|
351
|
+
- attribute_id: validate_number_cmd
|
|
352
|
+
name: Validate `a` input
|
|
353
|
+
data_key: a
|
|
354
|
+
params:
|
|
355
|
+
value: $r.a
|
|
356
|
+
- attribute_id: validate_number_cmd
|
|
357
|
+
name: Validate `b` input
|
|
358
|
+
data_key: b
|
|
359
|
+
params:
|
|
360
|
+
value: $r.b
|
|
361
|
+
- attribute_id: add_number_cmd
|
|
362
|
+
name: Add `a` and `b`
|
|
363
|
+
calc.subtract:
|
|
364
|
+
name: 'Subtract Number'
|
|
365
|
+
description: 'Subtracts one number from another'
|
|
366
|
+
commands:
|
|
367
|
+
- attribute_id: validate_number_cmd
|
|
368
|
+
name: Validate `a` input
|
|
369
|
+
data_key: a
|
|
370
|
+
params:
|
|
371
|
+
value: $r.a
|
|
372
|
+
- attribute_id: validate_number_cmd
|
|
373
|
+
name: Validate `b` input
|
|
374
|
+
data_key: b
|
|
375
|
+
params:
|
|
376
|
+
value: $r.b
|
|
377
|
+
- attribute_id: subtract_number_cmd
|
|
378
|
+
name: Subtract `b` from `a`
|
|
379
|
+
calc.multiply:
|
|
380
|
+
name: 'Multiply Number'
|
|
381
|
+
description: 'Multiplies one number by another'
|
|
382
|
+
commands:
|
|
383
|
+
- attribute_id: validate_number_cmd
|
|
384
|
+
name: Validate `a` input
|
|
385
|
+
data_key: a
|
|
386
|
+
params:
|
|
387
|
+
value: $r.a
|
|
388
|
+
- attribute_id: validate_number_cmd
|
|
389
|
+
name: Validate `b` input
|
|
390
|
+
data_key: b
|
|
391
|
+
params:
|
|
392
|
+
value: $r.b
|
|
393
|
+
- attribute_id: multiply_number_cmd
|
|
394
|
+
name: Multiply `a` and `b`
|
|
395
|
+
calc.divide:
|
|
396
|
+
name: 'Divide Number'
|
|
397
|
+
description: 'Divides one number by another'
|
|
398
|
+
commands:
|
|
399
|
+
- attribute_id: validate_number_cmd
|
|
400
|
+
name: Validate `a` input
|
|
401
|
+
data_key: a
|
|
402
|
+
params:
|
|
403
|
+
value: $r.a
|
|
404
|
+
- attribute_id: validate_number_cmd
|
|
405
|
+
name: Validate `b` input
|
|
406
|
+
data_key: b
|
|
407
|
+
params:
|
|
408
|
+
value: $r.b
|
|
409
|
+
- attribute_id: divide_number_cmd
|
|
410
|
+
name: Divide `a` by `b`
|
|
411
|
+
calc.exp:
|
|
412
|
+
name: 'Exponentiate Number'
|
|
413
|
+
description: 'Raises one number to the power of another'
|
|
414
|
+
commands:
|
|
415
|
+
- attribute_id: validate_number_cmd
|
|
416
|
+
name: Validate `a` input
|
|
417
|
+
data_key: a
|
|
418
|
+
params:
|
|
419
|
+
value: $r.a
|
|
420
|
+
- attribute_id: validate_number_cmd
|
|
421
|
+
name: Validate `b` input
|
|
422
|
+
data_key: b
|
|
423
|
+
params:
|
|
424
|
+
value: $r.b
|
|
425
|
+
- attribute_id: exponentiate_number_cmd
|
|
426
|
+
name: Raise `a` to the power of `b`
|
|
427
|
+
calc.sqrt:
|
|
428
|
+
name: 'Square Root'
|
|
429
|
+
description: 'Calculates the square root of a number'
|
|
430
|
+
commands:
|
|
431
|
+
- attribute_id: validate_number_cmd
|
|
432
|
+
name: Validate `a` input
|
|
433
|
+
data_key: a
|
|
434
|
+
params:
|
|
435
|
+
value: $r.a
|
|
436
|
+
- attribute_id: validate_number_cmd
|
|
437
|
+
name: Convert `b` to number
|
|
438
|
+
data_key: b
|
|
439
|
+
params:
|
|
440
|
+
value: '0.5'
|
|
441
|
+
- attribute_id: exponentiate_number_cmd
|
|
442
|
+
name: Calculate square root of `a`
|
|
443
|
+
|
|
444
|
+
errors:
|
|
445
|
+
invalid_input:
|
|
446
|
+
name: Invalid Numeric Input
|
|
447
|
+
message:
|
|
448
|
+
- lang: en_US
|
|
449
|
+
text: 'Value {} must be a number'
|
|
450
|
+
- lang: es_ES
|
|
451
|
+
text: 'El valor {} debe ser un número'
|
|
452
|
+
division_by_zero:
|
|
453
|
+
name: Division By Zero
|
|
454
|
+
message:
|
|
455
|
+
- lang: en_US
|
|
456
|
+
text: 'Cannot divide by zero'
|
|
457
|
+
- lang: es_ES
|
|
458
|
+
text: 'No se puede dividir por cero'
|
|
459
|
+
|
|
460
|
+
interfaces:
|
|
461
|
+
basic_calc:
|
|
462
|
+
name: Basic Calculator
|
|
463
|
+
description: Perform basic calculator operations
|
|
464
|
+
const:
|
|
465
|
+
container_config_file: 'app/configs/config.yml'
|
|
466
|
+
feature_config_file: 'app/configs/config.yml'
|
|
467
|
+
error_config_file: 'app/configs/config.yml'
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
`attrs`: Defines container attributes for dependency injection, mapping to command classes (e.g., `add_number_cmd` to `AddNumber`).
|
|
471
|
+
|
|
472
|
+
`features`: Configures feature workflows, sequencing validation and arithmetic commands (e.g., `calc.add` validates `a` and `b`, then adds them). The `calc.sqrt` feature reuses `exponentiate_number_cmd` with `b: "0.5"` for square roots.
|
|
473
|
+
|
|
474
|
+
`errors`: Specifies error messages for `invalid_input` and `division_by_zero`, supporting `en_US` and `es_ES` for multilingual extensibility.
|
|
475
|
+
|
|
476
|
+
`interfaces`: Defines the `basic_calc` interface instance, linking to the configuration file for container, features, and errors.
|
|
477
|
+
|
|
478
|
+
### Initializing and Demonstrating the Calculator in basic_calc.py
|
|
479
|
+
Finally, we initialize the calculator with an initializer script, `basic_calc.py`, at the project root. This script uses Tiferet’s App class to load the `basic_calc` context and execute features, demonstrating the calculator’s functionality.
|
|
480
|
+
Create `basic_calc.py` with the following content:
|
|
481
|
+
|
|
482
|
+
```python
|
|
483
|
+
from tiferet import App
|
|
484
|
+
|
|
485
|
+
# Create new app (manager) instance.
|
|
486
|
+
app = App(dict(
|
|
487
|
+
app_repo_module_path='tiferet.proxies.yaml.app',
|
|
488
|
+
app_repo_class_name='AppYamlProxy',
|
|
489
|
+
app_repo_params=dict(
|
|
490
|
+
app_config_file='app/configs/config.yml',
|
|
491
|
+
)
|
|
492
|
+
))
|
|
493
|
+
|
|
494
|
+
# Execute the add feature to add the values.
|
|
495
|
+
a = 1
|
|
496
|
+
b = 2
|
|
497
|
+
addition = app.run(
|
|
498
|
+
'basic_calc',
|
|
499
|
+
'calc.add',
|
|
500
|
+
data=dict(
|
|
501
|
+
a=a,
|
|
502
|
+
b=b,
|
|
503
|
+
)
|
|
504
|
+
)
|
|
505
|
+
|
|
506
|
+
print(f'{a} + {b} = {addition}')
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
### Demonstrating the Calculator
|
|
510
|
+
To run the calculator, ensure your `tiferet_app` virtual environment is activated and Tiferet is installed. Execute the initializer script:
|
|
511
|
+
```bash
|
|
512
|
+
python basic_calc
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
### Running the Calculator as a CLI
|
|
516
|
+
For a flexible and scriptable interface, the calculator includes a command-line interface (CLI) implemented in `calc_cli.py` at the project root. This script complements the `basic_calc.py` test script, which remains available for debugging and simple feature execution. The `calc_cli.py` script leverages Tiferet’s App class to execute features defined in `app/configs/config.yml`, accepting command-line arguments for operations and input values. It supports all calculator features: addition (calc.add), subtraction (calc.subtract), multiplication (`calc.multiply`), division (`calc.divide`), exponentiation (`calc.exp`), and square root (`calc.sqrt`).
|
|
517
|
+
The `calc_cli.py` script uses Python’s argparse to define subcommands for each feature, with required arguments `-a` (first number) and `-b` (second number, except for sqrt). The script executes the specified feature in the `basic_calc` context, returning the result as an integer or float.
|
|
518
|
+
|
|
519
|
+
Create `calc_cli.py` with the following content:
|
|
520
|
+
```python
|
|
521
|
+
import argparse
|
|
522
|
+
from tiferet import App, TiferetError
|
|
523
|
+
|
|
524
|
+
def main():
|
|
525
|
+
"""Parse CLI arguments and execute the calculator feature."""
|
|
526
|
+
parser = argparse.ArgumentParser(description="Basic Calculator CLI using Tiferet")
|
|
527
|
+
parser.add_argument('--config', default='app/configs/config.yml', help='Path to config file')
|
|
528
|
+
|
|
529
|
+
subparsers = parser.add_subparsers(dest='operation', required=True, help='Calculator operation')
|
|
530
|
+
|
|
531
|
+
# Define subcommands for each feature
|
|
532
|
+
operations = [
|
|
533
|
+
('add', 'Add two numbers', True),
|
|
534
|
+
('subtract', 'Subtract one number from another', True),
|
|
535
|
+
('multiply', 'Multiply two numbers', True),
|
|
536
|
+
('divide', 'Divide one number by another', True),
|
|
537
|
+
('exp', 'Raise one number to the power of another', True),
|
|
538
|
+
('sqrt', 'Calculate the square root of a number', False),
|
|
539
|
+
]
|
|
540
|
+
|
|
541
|
+
for op, help_text, needs_b in operations:
|
|
542
|
+
subparser = subparsers.add_parser(op, help=help_text)
|
|
543
|
+
subparser.add_argument('-a', required=True, help='First number')
|
|
544
|
+
if needs_b:
|
|
545
|
+
subparser.add_argument('-b', required=True, help='Second number')
|
|
546
|
+
|
|
547
|
+
args = parser.parse_args()
|
|
548
|
+
|
|
549
|
+
# Map operation to feature ID
|
|
550
|
+
feature_map = {
|
|
551
|
+
'add': 'calc.add',
|
|
552
|
+
'subtract': 'calc.subtract',
|
|
553
|
+
'multiply': 'calc.multiply',
|
|
554
|
+
'divide': 'calc.divide',
|
|
555
|
+
'exp': 'calc.exp',
|
|
556
|
+
'sqrt': 'calc.sqrt',
|
|
557
|
+
}
|
|
558
|
+
feature_id = feature_map[args.operation]
|
|
559
|
+
|
|
560
|
+
# Prepare feature parameters
|
|
561
|
+
params = {'a': str(args.a)}
|
|
562
|
+
if args.operation != 'sqrt':
|
|
563
|
+
params['b'] = str(args.b)
|
|
564
|
+
|
|
565
|
+
# Create app instance
|
|
566
|
+
# # Assume the default app settings is defined in a YAML file
|
|
567
|
+
settings = dict(
|
|
568
|
+
app_repo_module_path='tiferet.proxies.yaml.app',
|
|
569
|
+
app_repo_class_name='AppYamlProxy',
|
|
570
|
+
app_repo_params=dict(
|
|
571
|
+
app_config_file=args.config,
|
|
572
|
+
)
|
|
573
|
+
)
|
|
574
|
+
app = App(settings)
|
|
575
|
+
|
|
576
|
+
try:
|
|
577
|
+
# Execute feature with locale
|
|
578
|
+
result = app.run('basic_calc', feature_id, data=params)
|
|
579
|
+
|
|
580
|
+
# Display result
|
|
581
|
+
if args.operation == 'sqrt':
|
|
582
|
+
print(f"√{args.a} = {result}")
|
|
583
|
+
else:
|
|
584
|
+
op_symbol = {'add': '+', 'subtract': '-', 'multiply': '*', 'divide': '/', 'exp': '^'}[args.operation]
|
|
585
|
+
print(f"{args.a} {op_symbol} {args.b} = {result}")
|
|
586
|
+
except TiferetError as e:
|
|
587
|
+
print(f"Error: {e.message}")
|
|
588
|
+
except Exception as e:
|
|
589
|
+
print(f"Unexpected error: {str(e)}")
|
|
590
|
+
|
|
591
|
+
if __name__ == '__main__':
|
|
592
|
+
main()
|
|
593
|
+
```
|
|
594
|
+
|
|
595
|
+
Run the CLI with commands like:
|
|
596
|
+
```bash
|
|
597
|
+
# Add two numbers (default en_US)
|
|
598
|
+
python calc_cli.py add -a 1 -b 2
|
|
599
|
+
# Output: 1 + 2 = 3
|
|
600
|
+
|
|
601
|
+
# Calculate square root (en_US)
|
|
602
|
+
python calc_cli.py sqrt -a 4
|
|
603
|
+
# Output: √4 = 2.0
|
|
604
|
+
|
|
605
|
+
# Invalid input (es_ES)
|
|
606
|
+
python calc_cli.py add -a abc -b 2 --locale es_ES
|
|
607
|
+
# Output: Error: El valor abc debe ser un número
|
|
608
|
+
|
|
609
|
+
# Division by zero (en_US)
|
|
610
|
+
python calc_cli.py divide -a 5 -b 0
|
|
611
|
+
# Output: Error: Cannot divide by zero
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
The `calc_cli.py` script is scriptable and integrates easily with shell scripts or external systems, making it a versatile interface for the calculator. For quick testing or debugging, use `basic_calc.py`, which executes a single feature (e.g., `calc.add`) with hardcoded values. The CLI’s argument-driven design allows precise control over operations, showcasing Tiferet's flexibility in run-time environments.
|
|
615
|
+
|
|
616
|
+
## Conclusion
|
|
617
|
+
This tutorial has woven together the elegance of Tiferet’s Domain-Driven Design framework to create a robust and extensible basic calculator. From defining the immutable Number model to crafting command classes for arithmetic and validation, configuring features and errors, and launching the application via both a test script (`basic_calc.py`) and a CLI (`calc_cli.py`), you’ve experienced Tiferet’s balance of clarity and power. The configuration-driven approach, with dependency injection and multilingual error handling, embodies the Kabbalistic beauty of purposeful design, making the calculator both functional and a joy to develop.
|
|
618
|
+
|
|
619
|
+
With the foundation laid, you can extend this application in many directions. Consider adding a terminal user interface (TUI) in a new script, `calc_tui.py`, to wrap `calc_cli.py` for interactive menu-driven operation. Explore a scientific calculator context (`sci_calc`) with advanced features like trigonometric functions, reusing the `Number` model or introducing new ones. Or integrate the calculator into larger systems, leveraging Tiferet’s modularity for domains like financial modeling or data processing. Whatever path you choose, Tiferet’s graceful framework will guide you to solutions that resonate with both purpose and precision.
|
|
620
|
+
To continue your journey, try running additional features with `calc_cli.py`, experiment with new feature configurations in `app/configs/config.yml`, or dive into Tiferet’s documentation for advanced DDD techniques. The beauty of Tiferet lies in its ability to transform complexity into clarity—may your creations reflect this harmony.
|
tiferet-1.0.0/setup.cfg
ADDED