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.
Files changed (59) hide show
  1. tiferet-1.0.0/LICENSE +28 -0
  2. tiferet-1.0.0/PKG-INFO +25 -0
  3. tiferet-1.0.0/README.md +620 -0
  4. tiferet-1.0.0/setup.cfg +4 -0
  5. tiferet-1.0.0/setup.py +39 -0
  6. tiferet-1.0.0/tiferet/__init__.py +6 -0
  7. tiferet-1.0.0/tiferet/clients/__init__.py +1 -0
  8. tiferet-1.0.0/tiferet/clients/yaml.py +102 -0
  9. tiferet-1.0.0/tiferet/commands/__init__.py +6 -0
  10. tiferet-1.0.0/tiferet/commands/app.py +102 -0
  11. tiferet-1.0.0/tiferet/commands/core.py +124 -0
  12. tiferet-1.0.0/tiferet/commands/dependencies.py +76 -0
  13. tiferet-1.0.0/tiferet/commands/settings.py +101 -0
  14. tiferet-1.0.0/tiferet/configs/__init__.py +5 -0
  15. tiferet-1.0.0/tiferet/configs/app.py +59 -0
  16. tiferet-1.0.0/tiferet/configs/error.py +152 -0
  17. tiferet-1.0.0/tiferet/configs/settings.py +37 -0
  18. tiferet-1.0.0/tiferet/contexts/__init__.py +0 -0
  19. tiferet-1.0.0/tiferet/contexts/app.py +281 -0
  20. tiferet-1.0.0/tiferet/contexts/cache.py +76 -0
  21. tiferet-1.0.0/tiferet/contexts/container.py +125 -0
  22. tiferet-1.0.0/tiferet/contexts/error.py +124 -0
  23. tiferet-1.0.0/tiferet/contexts/feature.py +167 -0
  24. tiferet-1.0.0/tiferet/contracts/__init__.py +4 -0
  25. tiferet-1.0.0/tiferet/contracts/app.py +141 -0
  26. tiferet-1.0.0/tiferet/contracts/cache.py +70 -0
  27. tiferet-1.0.0/tiferet/contracts/container.py +147 -0
  28. tiferet-1.0.0/tiferet/contracts/error.py +177 -0
  29. tiferet-1.0.0/tiferet/contracts/feature.py +159 -0
  30. tiferet-1.0.0/tiferet/contracts/settings.py +34 -0
  31. tiferet-1.0.0/tiferet/data/__init__.py +4 -0
  32. tiferet-1.0.0/tiferet/data/app.py +148 -0
  33. tiferet-1.0.0/tiferet/data/container.py +227 -0
  34. tiferet-1.0.0/tiferet/data/error.py +89 -0
  35. tiferet-1.0.0/tiferet/data/feature.py +127 -0
  36. tiferet-1.0.0/tiferet/data/settings.py +159 -0
  37. tiferet-1.0.0/tiferet/handlers/__init__.py +0 -0
  38. tiferet-1.0.0/tiferet/handlers/app.py +120 -0
  39. tiferet-1.0.0/tiferet/handlers/container.py +119 -0
  40. tiferet-1.0.0/tiferet/handlers/error.py +49 -0
  41. tiferet-1.0.0/tiferet/handlers/feature.py +94 -0
  42. tiferet-1.0.0/tiferet/models/__init__.py +4 -0
  43. tiferet-1.0.0/tiferet/models/app.py +153 -0
  44. tiferet-1.0.0/tiferet/models/container.py +135 -0
  45. tiferet-1.0.0/tiferet/models/error.py +204 -0
  46. tiferet-1.0.0/tiferet/models/feature.py +244 -0
  47. tiferet-1.0.0/tiferet/models/settings.py +148 -0
  48. tiferet-1.0.0/tiferet/proxies/__init__.py +0 -0
  49. tiferet-1.0.0/tiferet/proxies/yaml/__init__.py +4 -0
  50. tiferet-1.0.0/tiferet/proxies/yaml/app.py +107 -0
  51. tiferet-1.0.0/tiferet/proxies/yaml/container.py +119 -0
  52. tiferet-1.0.0/tiferet/proxies/yaml/core.py +58 -0
  53. tiferet-1.0.0/tiferet/proxies/yaml/error.py +138 -0
  54. tiferet-1.0.0/tiferet/proxies/yaml/feature.py +119 -0
  55. tiferet-1.0.0/tiferet.egg-info/PKG-INFO +25 -0
  56. tiferet-1.0.0/tiferet.egg-info/SOURCES.txt +57 -0
  57. tiferet-1.0.0/tiferet.egg-info/dependency_links.txt +1 -0
  58. tiferet-1.0.0/tiferet.egg-info/requires.txt +7 -0
  59. 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
@@ -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.
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+