tiferet 1.0.0b0__tar.gz → 1.0.0b2__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.0b0/tiferet.egg-info → tiferet-1.0.0b2}/PKG-INFO +1 -1
- {tiferet-1.0.0b0 → tiferet-1.0.0b2}/README.md +123 -63
- {tiferet-1.0.0b0 → tiferet-1.0.0b2}/setup.py +1 -1
- {tiferet-1.0.0b0 → tiferet-1.0.0b2}/tiferet/contexts/feature.py +6 -4
- {tiferet-1.0.0b0 → tiferet-1.0.0b2}/tiferet/handlers/container.py +12 -9
- {tiferet-1.0.0b0 → tiferet-1.0.0b2/tiferet.egg-info}/PKG-INFO +1 -1
- {tiferet-1.0.0b0 → tiferet-1.0.0b2}/LICENSE +0 -0
- {tiferet-1.0.0b0 → tiferet-1.0.0b2}/setup.cfg +0 -0
- {tiferet-1.0.0b0 → tiferet-1.0.0b2}/tiferet/__init__.py +0 -0
- {tiferet-1.0.0b0 → tiferet-1.0.0b2}/tiferet/clients/__init__.py +0 -0
- {tiferet-1.0.0b0 → tiferet-1.0.0b2}/tiferet/clients/yaml.py +0 -0
- {tiferet-1.0.0b0 → tiferet-1.0.0b2}/tiferet/commands/__init__.py +0 -0
- {tiferet-1.0.0b0 → tiferet-1.0.0b2}/tiferet/commands/app.py +0 -0
- {tiferet-1.0.0b0 → tiferet-1.0.0b2}/tiferet/commands/core.py +0 -0
- {tiferet-1.0.0b0 → tiferet-1.0.0b2}/tiferet/commands/dependencies.py +0 -0
- {tiferet-1.0.0b0 → tiferet-1.0.0b2}/tiferet/commands/settings.py +0 -0
- {tiferet-1.0.0b0 → tiferet-1.0.0b2}/tiferet/configs/__init__.py +0 -0
- {tiferet-1.0.0b0 → tiferet-1.0.0b2}/tiferet/configs/error.py +0 -0
- {tiferet-1.0.0b0 → tiferet-1.0.0b2}/tiferet/configs/settings.py +0 -0
- {tiferet-1.0.0b0 → tiferet-1.0.0b2}/tiferet/contexts/__init__.py +0 -0
- {tiferet-1.0.0b0 → tiferet-1.0.0b2}/tiferet/contexts/app.py +0 -0
- {tiferet-1.0.0b0 → tiferet-1.0.0b2}/tiferet/contexts/cache.py +0 -0
- {tiferet-1.0.0b0 → tiferet-1.0.0b2}/tiferet/contexts/container.py +0 -0
- {tiferet-1.0.0b0 → tiferet-1.0.0b2}/tiferet/contexts/error.py +0 -0
- {tiferet-1.0.0b0 → tiferet-1.0.0b2}/tiferet/contracts/__init__.py +0 -0
- {tiferet-1.0.0b0 → tiferet-1.0.0b2}/tiferet/contracts/app.py +0 -0
- {tiferet-1.0.0b0 → tiferet-1.0.0b2}/tiferet/contracts/cache.py +0 -0
- {tiferet-1.0.0b0 → tiferet-1.0.0b2}/tiferet/contracts/container.py +0 -0
- {tiferet-1.0.0b0 → tiferet-1.0.0b2}/tiferet/contracts/error.py +0 -0
- {tiferet-1.0.0b0 → tiferet-1.0.0b2}/tiferet/contracts/feature.py +0 -0
- {tiferet-1.0.0b0 → tiferet-1.0.0b2}/tiferet/contracts/settings.py +0 -0
- {tiferet-1.0.0b0 → tiferet-1.0.0b2}/tiferet/data/__init__.py +0 -0
- {tiferet-1.0.0b0 → tiferet-1.0.0b2}/tiferet/data/app.py +0 -0
- {tiferet-1.0.0b0 → tiferet-1.0.0b2}/tiferet/data/container.py +0 -0
- {tiferet-1.0.0b0 → tiferet-1.0.0b2}/tiferet/data/error.py +0 -0
- {tiferet-1.0.0b0 → tiferet-1.0.0b2}/tiferet/data/feature.py +0 -0
- {tiferet-1.0.0b0 → tiferet-1.0.0b2}/tiferet/data/settings.py +0 -0
- {tiferet-1.0.0b0 → tiferet-1.0.0b2}/tiferet/domain/__init__.py +0 -0
- {tiferet-1.0.0b0 → tiferet-1.0.0b2}/tiferet/domain/app.py +0 -0
- {tiferet-1.0.0b0 → tiferet-1.0.0b2}/tiferet/domain/container.py +0 -0
- {tiferet-1.0.0b0 → tiferet-1.0.0b2}/tiferet/domain/core.py +0 -0
- {tiferet-1.0.0b0 → tiferet-1.0.0b2}/tiferet/domain/error.py +0 -0
- {tiferet-1.0.0b0 → tiferet-1.0.0b2}/tiferet/domain/feature.py +0 -0
- {tiferet-1.0.0b0 → tiferet-1.0.0b2}/tiferet/handlers/__init__.py +0 -0
- {tiferet-1.0.0b0 → tiferet-1.0.0b2}/tiferet/handlers/error.py +0 -0
- {tiferet-1.0.0b0 → tiferet-1.0.0b2}/tiferet/handlers/feature.py +0 -0
- {tiferet-1.0.0b0 → tiferet-1.0.0b2}/tiferet/models/__init__.py +0 -0
- {tiferet-1.0.0b0 → tiferet-1.0.0b2}/tiferet/models/app.py +0 -0
- {tiferet-1.0.0b0 → tiferet-1.0.0b2}/tiferet/models/container.py +0 -0
- {tiferet-1.0.0b0 → tiferet-1.0.0b2}/tiferet/models/error.py +0 -0
- {tiferet-1.0.0b0 → tiferet-1.0.0b2}/tiferet/models/feature.py +0 -0
- {tiferet-1.0.0b0 → tiferet-1.0.0b2}/tiferet/models/settings.py +0 -0
- {tiferet-1.0.0b0 → tiferet-1.0.0b2}/tiferet/proxies/__init__.py +0 -0
- {tiferet-1.0.0b0 → tiferet-1.0.0b2}/tiferet/proxies/yaml/__init__.py +0 -0
- {tiferet-1.0.0b0 → tiferet-1.0.0b2}/tiferet/proxies/yaml/app.py +0 -0
- {tiferet-1.0.0b0 → tiferet-1.0.0b2}/tiferet/proxies/yaml/container.py +0 -0
- {tiferet-1.0.0b0 → tiferet-1.0.0b2}/tiferet/proxies/yaml/error.py +0 -0
- {tiferet-1.0.0b0 → tiferet-1.0.0b2}/tiferet/proxies/yaml/feature.py +0 -0
- {tiferet-1.0.0b0 → tiferet-1.0.0b2}/tiferet.egg-info/SOURCES.txt +0 -0
- {tiferet-1.0.0b0 → tiferet-1.0.0b2}/tiferet.egg-info/dependency_links.txt +0 -0
- {tiferet-1.0.0b0 → tiferet-1.0.0b2}/tiferet.egg-info/requires.txt +0 -0
- {tiferet-1.0.0b0 → tiferet-1.0.0b2}/tiferet.egg-info/top_level.txt +0 -0
@@ -39,7 +39,7 @@ python3.10 --version
|
|
39
39
|
You should see Python 3.10.x if successful.
|
40
40
|
|
41
41
|
### Setting Up a Virtual Environment
|
42
|
-
To keep your project dependencies organized, create a virtual environment named tiferet_app for your calculator application:
|
42
|
+
To keep your project dependencies organized, create a virtual environment named `tiferet_app` for your calculator application:
|
43
43
|
|
44
44
|
#### Create the Environment
|
45
45
|
|
@@ -65,13 +65,13 @@ tiferet_app\Scripts\activate
|
|
65
65
|
source tiferet_app/bin/activate
|
66
66
|
```
|
67
67
|
|
68
|
-
Your terminal should display (tiferet_app)
|
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
69
|
Deactivate the Environment
|
70
70
|
When finished, deactivate the environment with:
|
71
71
|
deactivate
|
72
72
|
|
73
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.
|
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
75
|
|
76
76
|
### Installing Tiferet
|
77
77
|
Install the Tiferet package using pip in your activated virtual environment:
|
@@ -90,6 +90,7 @@ Create a project directory structure to organize your calculator application:
|
|
90
90
|
```plaintext
|
91
91
|
project_root/
|
92
92
|
├── basic_calc.py
|
93
|
+
├── calc_cli.py
|
93
94
|
└── app/
|
94
95
|
├── commands/
|
95
96
|
│ ├── __init__.py
|
@@ -97,21 +98,21 @@ project_root/
|
|
97
98
|
│ └── valid.py
|
98
99
|
├── configs/
|
99
100
|
│ ├── __init__.py
|
100
|
-
│ └── config.
|
101
|
+
│ └── config.yml
|
101
102
|
└── models/
|
102
103
|
├── __init__.py
|
103
104
|
└── calc.py
|
104
105
|
```
|
105
106
|
|
106
|
-
The app/models
|
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.
|
107
108
|
|
108
109
|
## Crafting the Calculator Application
|
109
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.
|
110
111
|
|
111
|
-
### Defining the Number Model in models/calc.py
|
112
|
-
The calculator’s numerical values are represented by a Number value object, defined in app/models/calc.py
|
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.
|
113
114
|
|
114
|
-
Create app/models/calc.py with the following content:
|
115
|
+
Create `app/models/calc.py` with the following content:
|
115
116
|
|
116
117
|
```python
|
117
118
|
from tiferet.models import *
|
@@ -147,24 +148,24 @@ class Number(ValueObject):
|
|
147
148
|
return int(self.value)
|
148
149
|
```
|
149
150
|
|
150
|
-
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"
|
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.
|
151
152
|
|
152
153
|
### Defining Command Classes in commands/calc.py and commands/valid.py
|
153
|
-
Next, we define command classes to perform arithmetic operations and input validation. Arithmetic commands (AddNumber
|
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.
|
154
155
|
|
155
156
|
#### Arithmetic Commands in commands/calc.py
|
156
|
-
Create app/commands/calc.py with the following content:
|
157
|
+
Create `app/commands/calc.py` with the following content:
|
157
158
|
|
158
159
|
```python
|
159
|
-
from
|
160
|
-
|
160
|
+
from tiferet.commands import *
|
161
|
+
|
161
162
|
from ..models.calc import Number
|
162
163
|
|
163
164
|
class AddNumber(Command):
|
164
165
|
'''
|
165
166
|
A command to perform addition of two numbers.
|
166
167
|
'''
|
167
|
-
def execute(self, a: Number, b: Number) -> Number:
|
168
|
+
def execute(self, a: Number, b: Number, **kwargs) -> Number:
|
168
169
|
'''
|
169
170
|
Execute the addition command.
|
170
171
|
|
@@ -172,16 +173,23 @@ class AddNumber(Command):
|
|
172
173
|
:type a: Number
|
173
174
|
:param b: A Number object representing the second number.
|
174
175
|
:type b: Number
|
176
|
+
:param kwargs: Additional keyword arguments.
|
177
|
+
:type kwargs: dict
|
175
178
|
:return: A Number object representing the sum of a and b.
|
176
179
|
:rtype: Number
|
177
180
|
'''
|
178
|
-
|
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
|
179
187
|
|
180
188
|
class SubtractNumber(Command):
|
181
189
|
'''
|
182
190
|
A command to perform subtraction of two numbers.
|
183
191
|
'''
|
184
|
-
def execute(self, a: Number, b: Number) -> Number:
|
192
|
+
def execute(self, a: Number, b: Number, **kwargs) -> Number:
|
185
193
|
'''
|
186
194
|
Execute the subtraction command.
|
187
195
|
|
@@ -189,16 +197,23 @@ class SubtractNumber(Command):
|
|
189
197
|
:type a: Number
|
190
198
|
:param b: A Number object representing the second number.
|
191
199
|
:type b: Number
|
200
|
+
:param kwargs: Additional keyword arguments.
|
201
|
+
:type kwargs: dict
|
192
202
|
:return: A Number object representing the difference of a and b.
|
193
203
|
:rtype: Number
|
194
204
|
'''
|
195
|
-
|
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
|
196
211
|
|
197
212
|
class MultiplyNumber(Command):
|
198
213
|
'''
|
199
214
|
A command to perform multiplication of two numbers.
|
200
215
|
'''
|
201
|
-
def execute(self, a: Number, b: Number) -> Number:
|
216
|
+
def execute(self, a: Number, b: Number, **kwargs) -> Number:
|
202
217
|
'''
|
203
218
|
Execute the multiplication command.
|
204
219
|
|
@@ -206,16 +221,23 @@ class MultiplyNumber(Command):
|
|
206
221
|
:type a: Number
|
207
222
|
:param b: A Number object representing the second number.
|
208
223
|
:type b: Number
|
224
|
+
:param kwargs: Additional keyword arguments.
|
225
|
+
:type kwargs: dict
|
209
226
|
:return: A Number object representing the product of a and b.
|
210
227
|
:rtype: Number
|
211
228
|
'''
|
212
|
-
|
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
|
213
235
|
|
214
236
|
class DivideNumber(Command):
|
215
237
|
'''
|
216
238
|
A command to perform division of two numbers.
|
217
239
|
'''
|
218
|
-
def execute(self, a: Number, b: Number) -> Number:
|
240
|
+
def execute(self, a: Number, b: Number, **kwargs) -> Number:
|
219
241
|
'''
|
220
242
|
Execute the division command.
|
221
243
|
|
@@ -223,17 +245,26 @@ class DivideNumber(Command):
|
|
223
245
|
:type a: Number
|
224
246
|
:param b: A Number object representing the second number, must be non-zero.
|
225
247
|
:type b: Number
|
248
|
+
:param kwargs: Additional keyword arguments.
|
249
|
+
:type kwargs: dict
|
226
250
|
:return: A Number object representing the quotient of a and b.
|
227
251
|
:rtype: Number
|
228
252
|
'''
|
253
|
+
# Check if b is zero to avoid division by zero.
|
229
254
|
self.verify(b.format() != 0, 'DIVISION_BY_ZERO')
|
230
|
-
|
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
|
+
|
231
262
|
|
232
263
|
class ExponentiateNumber(Command):
|
233
264
|
'''
|
234
265
|
A command to perform exponentiation of two numbers.
|
235
266
|
'''
|
236
|
-
def execute(self, a: Number, b: Number) -> Number:
|
267
|
+
def execute(self, a: Number, b: Number, **kwargs) -> Number:
|
237
268
|
'''
|
238
269
|
Execute the exponentiation command.
|
239
270
|
|
@@ -241,45 +272,55 @@ class ExponentiateNumber(Command):
|
|
241
272
|
:type a: Number
|
242
273
|
:param b: A Number object representing the exponent.
|
243
274
|
:type b: Number
|
275
|
+
:param kwargs: Additional keyword arguments.
|
276
|
+
:type kwargs: dict
|
244
277
|
:return: A Number object representing a raised to the power of b.
|
245
278
|
:rtype: Number
|
246
279
|
'''
|
247
|
-
|
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
|
248
286
|
```
|
249
287
|
|
250
|
-
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.
|
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.
|
251
289
|
|
252
|
-
#### Validation Command in commands/valid.py
|
253
|
-
Create app/commands/valid.py with the following content:
|
290
|
+
#### Validation Command in `commands/valid.py`
|
291
|
+
Create `app/commands/valid.py` with the following content:
|
254
292
|
|
255
293
|
```python
|
256
|
-
from
|
294
|
+
from tiferet.commands import *
|
295
|
+
|
257
296
|
from ..models.calc import Number
|
258
297
|
|
259
298
|
class ValidateNumber(Command):
|
260
299
|
'''
|
261
300
|
A command to validate that a value can be a Number object.
|
262
301
|
'''
|
263
|
-
def execute(self, value: str) -> None:
|
302
|
+
def execute(self, value: str, **kwargs) -> None:
|
264
303
|
'''
|
265
304
|
Validate that the input value can be used to create a Number object.
|
266
305
|
|
267
306
|
:param value: Any string value to validate.
|
268
307
|
:type value: str
|
308
|
+
:param kwargs: Additional keyword arguments.
|
309
|
+
:type kwargs: dict
|
269
310
|
:raises TiferetError: If the value cannot be a Number.
|
270
311
|
'''
|
271
312
|
try:
|
272
|
-
ModelObject.new(Number, value=str(value))
|
313
|
+
return ModelObject.new(Number, value=str(value))
|
273
314
|
except Exception as e:
|
274
315
|
self.verify(False, 'INVALID_INPUT', value)
|
275
316
|
```
|
276
317
|
|
277
|
-
The ValidateNumber command ensures inputs can be converted to Number objects, raising a configured error for invalid values.
|
318
|
+
The `ValidateNumber` command ensures inputs can be converted to `Number` objects, raising a configured error for invalid values.
|
278
319
|
|
279
|
-
### Configuring the Application in configs/config.
|
280
|
-
The calculator’s behavior is defined in app/configs/config.
|
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.
|
281
322
|
|
282
|
-
Create `app/configs/config.
|
323
|
+
Create `app/configs/config.yml` with the following content:
|
283
324
|
|
284
325
|
```yaml
|
285
326
|
attrs:
|
@@ -416,54 +457,66 @@ errors:
|
|
416
457
|
- lang: es_ES
|
417
458
|
text: 'No se puede dividir por cero'
|
418
459
|
|
419
|
-
|
460
|
+
interfaces:
|
420
461
|
basic_calc:
|
421
462
|
name: Basic Calculator
|
422
463
|
description: Perform basic calculator operations
|
423
464
|
const:
|
424
|
-
container_config_file: 'app/configs/config.
|
425
|
-
feature_config_file: 'app/configs/config.
|
426
|
-
error_config_file: 'app/configs/config.
|
465
|
+
container_config_file: 'app/configs/config.yml'
|
466
|
+
feature_config_file: 'app/configs/config.yml'
|
467
|
+
error_config_file: 'app/configs/config.yml'
|
427
468
|
```
|
428
469
|
|
429
|
-
attrs
|
470
|
+
`attrs`: Defines container attributes for dependency injection, mapping to command classes (e.g., `add_number_cmd` to `AddNumber`).
|
430
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.
|
431
473
|
|
432
|
-
|
474
|
+
`errors`: Specifies error messages for `invalid_input` and `division_by_zero`, supporting `en_US` and `es_ES` for multilingual extensibility.
|
433
475
|
|
434
|
-
|
435
|
-
|
436
|
-
contexts: Defines the basic_calc application instance, linking to the configuration file for container, features, and errors.
|
476
|
+
`interfaces`: Defines the `basic_calc` interface instance, linking to the configuration file for container, features, and errors.
|
437
477
|
|
438
478
|
### Initializing and Demonstrating the Calculator in basic_calc.py
|
439
|
-
Finally, we initialize the calculator with an initializer script, basic_calc.py
|
440
|
-
Create basic_calc.py with the following content:
|
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:
|
441
481
|
|
442
482
|
```python
|
443
483
|
from tiferet import App
|
444
484
|
|
445
485
|
# Create new app (manager) instance.
|
446
|
-
app = App(
|
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
|
+
))
|
447
493
|
|
448
494
|
# Execute the add feature to add the values.
|
449
495
|
a = 1
|
450
496
|
b = 2
|
451
|
-
addition = app.
|
497
|
+
addition = app.run(
|
498
|
+
'basic_calc',
|
499
|
+
'calc.add',
|
500
|
+
data=dict(
|
501
|
+
a=a,
|
502
|
+
b=b,
|
503
|
+
)
|
504
|
+
)
|
452
505
|
|
453
|
-
print(f'{a} + {b} = {addition
|
506
|
+
print(f'{a} + {b} = {addition}')
|
454
507
|
```
|
455
508
|
|
456
509
|
### Demonstrating the Calculator
|
457
|
-
To run the calculator, ensure your tiferet_app virtual environment is activated and Tiferet is installed. Execute the initializer script:
|
510
|
+
To run the calculator, ensure your `tiferet_app` virtual environment is activated and Tiferet is installed. Execute the initializer script:
|
458
511
|
```bash
|
459
512
|
python basic_calc
|
460
513
|
```
|
461
514
|
|
462
515
|
### Running the Calculator as a CLI
|
463
|
-
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.
|
464
|
-
The calc_cli.py script uses Python’s argparse to define subcommands for each feature, with required arguments
|
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.
|
465
518
|
|
466
|
-
Create calc_cli.py with the following content:
|
519
|
+
Create `calc_cli.py` with the following content:
|
467
520
|
```python
|
468
521
|
import argparse
|
469
522
|
from tiferet import App, TiferetError
|
@@ -471,8 +524,7 @@ from tiferet import App, TiferetError
|
|
471
524
|
def main():
|
472
525
|
"""Parse CLI arguments and execute the calculator feature."""
|
473
526
|
parser = argparse.ArgumentParser(description="Basic Calculator CLI using Tiferet")
|
474
|
-
parser.add_argument('--config', default='app/configs/config.
|
475
|
-
parser.add_argument('--locale', default='en_US', choices=['en_US', 'es_ES'], help='Language for error messages')
|
527
|
+
parser.add_argument('--config', default='app/configs/config.yml', help='Path to config file')
|
476
528
|
|
477
529
|
subparsers = parser.add_subparsers(dest='operation', required=True, help='Calculator operation')
|
478
530
|
|
@@ -494,9 +546,6 @@ def main():
|
|
494
546
|
|
495
547
|
args = parser.parse_args()
|
496
548
|
|
497
|
-
# Create app instance
|
498
|
-
app = App(config_file=args.config)
|
499
|
-
|
500
549
|
# Map operation to feature ID
|
501
550
|
feature_map = {
|
502
551
|
'add': 'calc.add',
|
@@ -513,16 +562,27 @@ def main():
|
|
513
562
|
if args.operation != 'sqrt':
|
514
563
|
params['b'] = str(args.b)
|
515
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
|
+
|
516
576
|
try:
|
517
577
|
# Execute feature with locale
|
518
|
-
result = app.
|
578
|
+
result = app.run('basic_calc', feature_id, data=params)
|
519
579
|
|
520
580
|
# Display result
|
521
581
|
if args.operation == 'sqrt':
|
522
|
-
print(f"√{args.a} = {result
|
582
|
+
print(f"√{args.a} = {result}")
|
523
583
|
else:
|
524
584
|
op_symbol = {'add': '+', 'subtract': '-', 'multiply': '*', 'divide': '/', 'exp': '^'}[args.operation]
|
525
|
-
print(f"{args.a} {op_symbol} {args.b} = {result
|
585
|
+
print(f"{args.a} {op_symbol} {args.b} = {result}")
|
526
586
|
except TiferetError as e:
|
527
587
|
print(f"Error: {e.message}")
|
528
588
|
except Exception as e:
|
@@ -551,10 +611,10 @@ python calc_cli.py divide -a 5 -b 0
|
|
551
611
|
# Output: Error: Cannot divide by zero
|
552
612
|
```
|
553
613
|
|
554
|
-
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
|
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.
|
555
615
|
|
556
616
|
## Conclusion
|
557
|
-
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.
|
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.
|
558
618
|
|
559
|
-
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
|
560
|
-
To continue your journey, try running additional features with calc_cli.py
|
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.
|
@@ -9,7 +9,7 @@ config = {
|
|
9
9
|
'url': r'https://github.com/greatstrength/app',
|
10
10
|
'download_url': r'https://github.com/greatstrength/app',
|
11
11
|
'author_email': 'andrew@greatstrength.me',
|
12
|
-
'version': '1.0.0-beta.
|
12
|
+
'version': '1.0.0-beta.2',
|
13
13
|
'license': 'BSD 3',
|
14
14
|
'install_requires': [
|
15
15
|
'schematics>=2.1.1',
|
@@ -101,8 +101,6 @@ class FeatureContext(object):
|
|
101
101
|
except Exception as e:
|
102
102
|
if not pass_on_error:
|
103
103
|
raise e
|
104
|
-
finally:
|
105
|
-
print(f'Command {command.attribute_id} execution failed: {e}' if 'e' in locals() else '')
|
106
104
|
|
107
105
|
# If a data key is provided, store the result in the request data.
|
108
106
|
if data_key:
|
@@ -140,21 +138,25 @@ class FeatureContext(object):
|
|
140
138
|
# Execute the feature with the request and commands.
|
141
139
|
for index, cmd in enumerate(commands):
|
142
140
|
|
141
|
+
# Get the feature command from the feature.
|
142
|
+
feature_command = feature.commands[index]
|
143
|
+
|
143
144
|
# Parse the command parameters
|
144
145
|
params = {
|
145
146
|
param: self.feature_service.parse_parameter(value, request)
|
146
|
-
for param, value in
|
147
|
+
for param, value in feature_command.parameters.items()
|
147
148
|
}
|
148
149
|
|
149
150
|
# Execute the command with the request data and parameters.
|
150
151
|
self.handle_command(
|
151
152
|
cmd,
|
152
153
|
request,
|
154
|
+
data_key=feature_command.data_key,
|
155
|
+
pass_on_error=feature_command.pass_on_error,
|
153
156
|
**params,
|
154
157
|
features=self.feature_service,
|
155
158
|
container=self.container,
|
156
159
|
cache=self.cache,
|
157
160
|
**kwargs
|
158
161
|
)
|
159
|
-
|
160
162
|
|
@@ -22,10 +22,8 @@ class ContainerHandler(ContainerService):
|
|
22
22
|
'''
|
23
23
|
Initialize the container handler.
|
24
24
|
|
25
|
-
:param
|
26
|
-
:type
|
27
|
-
:param dependencies: The dependencies.
|
28
|
-
:type dependencies: dict
|
25
|
+
:param container_repo: The container repository to use for managing container attributes.
|
26
|
+
:type container_repo: ContainerRepository
|
29
27
|
'''
|
30
28
|
|
31
29
|
# Assign the container repository.
|
@@ -69,15 +67,20 @@ class ContainerHandler(ContainerService):
|
|
69
67
|
# If constants are provided, clean the parameters using the parse_parameter command.
|
70
68
|
constants = {k: parse_parameter.execute(v) for k, v in constants.items()}
|
71
69
|
|
72
|
-
# Iterate through each attribute
|
73
|
-
# For each attribute, parse its parameters and add them to the constants dictionary.
|
74
|
-
# For each dependency, parse its parameters and add them to the constants dictionary.
|
70
|
+
# Iterate through each attribute.
|
75
71
|
for attr in attributes:
|
76
|
-
|
77
|
-
|
72
|
+
|
73
|
+
# If flags are provided, check for dependencies with those flags.
|
74
|
+
dependency = attr.get_dependency(*flags)
|
75
|
+
|
76
|
+
# Update the constants dictionary with the parsed parameters from the dependency or the attribute itself.
|
78
77
|
if dependency:
|
79
78
|
constants.update({k: parse_parameter.execute(v) for k, v in dependency.parameters.items()})
|
80
79
|
|
80
|
+
# If no dependency is found, use the attribute's parameters.
|
81
|
+
else:
|
82
|
+
constants.update({k: parse_parameter.execute(v) for k, v in attr.parameters.items()})
|
83
|
+
|
81
84
|
# Return the updated constants dictionary.
|
82
85
|
return constants
|
83
86
|
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|