tiferet 1.0.0a18__tar.gz → 1.0.0b0__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 (79) hide show
  1. {tiferet-1.0.0a18/tiferet.egg-info → tiferet-1.0.0b0}/PKG-INFO +12 -3
  2. tiferet-1.0.0b0/README.md +560 -0
  3. {tiferet-1.0.0a18 → tiferet-1.0.0b0}/setup.py +7 -4
  4. tiferet-1.0.0b0/tiferet/__init__.py +6 -0
  5. tiferet-1.0.0b0/tiferet/commands/__init__.py +6 -0
  6. tiferet-1.0.0b0/tiferet/commands/app.py +102 -0
  7. tiferet-1.0.0b0/tiferet/commands/core.py +124 -0
  8. tiferet-1.0.0b0/tiferet/commands/dependencies.py +76 -0
  9. tiferet-1.0.0b0/tiferet/commands/settings.py +101 -0
  10. tiferet-1.0.0b0/tiferet/configs/__init__.py +5 -0
  11. tiferet-1.0.0b0/tiferet/configs/error.py +48 -0
  12. tiferet-1.0.0b0/tiferet/configs/settings.py +37 -0
  13. tiferet-1.0.0b0/tiferet/contexts/app.py +322 -0
  14. tiferet-1.0.0b0/tiferet/contexts/cache.py +76 -0
  15. tiferet-1.0.0b0/tiferet/contexts/container.py +134 -0
  16. tiferet-1.0.0b0/tiferet/contexts/error.py +124 -0
  17. tiferet-1.0.0b0/tiferet/contexts/feature.py +160 -0
  18. tiferet-1.0.0b0/tiferet/contracts/__init__.py +4 -0
  19. tiferet-1.0.0b0/tiferet/contracts/app.py +92 -0
  20. tiferet-1.0.0b0/tiferet/contracts/cache.py +70 -0
  21. tiferet-1.0.0b0/tiferet/contracts/container.py +147 -0
  22. tiferet-1.0.0b0/tiferet/contracts/error.py +177 -0
  23. tiferet-1.0.0b0/tiferet/contracts/feature.py +159 -0
  24. tiferet-1.0.0b0/tiferet/contracts/settings.py +34 -0
  25. tiferet-1.0.0b0/tiferet/data/__init__.py +4 -0
  26. tiferet-1.0.0b0/tiferet/data/app.py +148 -0
  27. {tiferet-1.0.0a18 → tiferet-1.0.0b0}/tiferet/data/container.py +52 -38
  28. {tiferet-1.0.0a18 → tiferet-1.0.0b0}/tiferet/data/error.py +15 -24
  29. {tiferet-1.0.0a18 → tiferet-1.0.0b0}/tiferet/data/feature.py +27 -8
  30. tiferet-1.0.0a18/tiferet/domain/core.py → tiferet-1.0.0b0/tiferet/data/settings.py +36 -96
  31. tiferet-1.0.0b0/tiferet/domain/app.py +6 -0
  32. tiferet-1.0.0b0/tiferet/domain/core.py +102 -0
  33. tiferet-1.0.0b0/tiferet/domain/feature.py +4 -0
  34. tiferet-1.0.0b0/tiferet/handlers/__init__.py +0 -0
  35. tiferet-1.0.0b0/tiferet/handlers/container.py +116 -0
  36. tiferet-1.0.0b0/tiferet/handlers/error.py +49 -0
  37. tiferet-1.0.0b0/tiferet/handlers/feature.py +94 -0
  38. tiferet-1.0.0b0/tiferet/models/__init__.py +4 -0
  39. tiferet-1.0.0b0/tiferet/models/app.py +150 -0
  40. tiferet-1.0.0b0/tiferet/models/container.py +135 -0
  41. tiferet-1.0.0b0/tiferet/models/error.py +204 -0
  42. {tiferet-1.0.0a18/tiferet/domain → tiferet-1.0.0b0/tiferet/models}/feature.py +107 -47
  43. tiferet-1.0.0b0/tiferet/models/settings.py +148 -0
  44. tiferet-1.0.0b0/tiferet/proxies/__init__.py +0 -0
  45. tiferet-1.0.0b0/tiferet/proxies/yaml/__init__.py +0 -0
  46. {tiferet-1.0.0a18/tiferet/repos → tiferet-1.0.0b0/tiferet/proxies/yaml}/app.py +13 -41
  47. {tiferet-1.0.0a18/tiferet/repos → tiferet-1.0.0b0/tiferet/proxies/yaml}/container.py +26 -56
  48. {tiferet-1.0.0a18/tiferet/repos → tiferet-1.0.0b0/tiferet/proxies/yaml}/error.py +11 -70
  49. tiferet-1.0.0b0/tiferet/proxies/yaml/feature.py +92 -0
  50. {tiferet-1.0.0a18 → tiferet-1.0.0b0/tiferet.egg-info}/PKG-INFO +12 -3
  51. tiferet-1.0.0b0/tiferet.egg-info/SOURCES.txt +60 -0
  52. tiferet-1.0.0a18/tiferet/__init__.py +0 -5
  53. tiferet-1.0.0a18/tiferet/commands/container.py +0 -54
  54. tiferet-1.0.0a18/tiferet/commands/error.py +0 -21
  55. tiferet-1.0.0a18/tiferet/commands/feature.py +0 -90
  56. tiferet-1.0.0a18/tiferet/configs/__init__.py +0 -69
  57. tiferet-1.0.0a18/tiferet/contexts/__init__.py +0 -8
  58. tiferet-1.0.0a18/tiferet/contexts/app.py +0 -289
  59. tiferet-1.0.0a18/tiferet/contexts/container.py +0 -232
  60. tiferet-1.0.0a18/tiferet/contexts/error.py +0 -121
  61. tiferet-1.0.0a18/tiferet/contexts/feature.py +0 -123
  62. tiferet-1.0.0a18/tiferet/contexts/request.py +0 -110
  63. tiferet-1.0.0a18/tiferet/data/__init__.py +0 -5
  64. tiferet-1.0.0a18/tiferet/data/app.py +0 -285
  65. tiferet-1.0.0a18/tiferet/domain/app.py +0 -131
  66. tiferet-1.0.0a18/tiferet/repos/__init__.py +0 -7
  67. tiferet-1.0.0a18/tiferet/repos/feature.py +0 -151
  68. tiferet-1.0.0a18/tiferet.egg-info/SOURCES.txt +0 -37
  69. {tiferet-1.0.0a18 → tiferet-1.0.0b0}/LICENSE +0 -0
  70. {tiferet-1.0.0a18 → tiferet-1.0.0b0}/setup.cfg +0 -0
  71. {tiferet-1.0.0a18 → tiferet-1.0.0b0}/tiferet/clients/__init__.py +0 -0
  72. {tiferet-1.0.0a18 → tiferet-1.0.0b0}/tiferet/clients/yaml.py +0 -0
  73. {tiferet-1.0.0a18/tiferet/commands → tiferet-1.0.0b0/tiferet/contexts}/__init__.py +0 -0
  74. {tiferet-1.0.0a18 → tiferet-1.0.0b0}/tiferet/domain/__init__.py +0 -0
  75. {tiferet-1.0.0a18 → tiferet-1.0.0b0}/tiferet/domain/container.py +0 -0
  76. {tiferet-1.0.0a18 → tiferet-1.0.0b0}/tiferet/domain/error.py +0 -0
  77. {tiferet-1.0.0a18 → tiferet-1.0.0b0}/tiferet.egg-info/dependency_links.txt +0 -0
  78. {tiferet-1.0.0a18 → tiferet-1.0.0b0}/tiferet.egg-info/requires.txt +0 -0
  79. {tiferet-1.0.0a18 → tiferet-1.0.0b0}/tiferet.egg-info/top_level.txt +0 -0
@@ -1,10 +1,10 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: tiferet
3
- Version: 1.0.0a18
3
+ Version: 1.0.0b0
4
4
  Summary: A multi-purpose application framework embodying beauty in form.
5
5
  Home-page: https://github.com/greatstrength/app
6
6
  Download-URL: https://github.com/greatstrength/app
7
- Author: Andrew Shatz
7
+ Author: Andrew Shatz, Great Strength Systems
8
8
  Author-email: andrew@greatstrength.me
9
9
  License: BSD 3
10
10
  License-File: LICENSE
@@ -14,3 +14,12 @@ Requires-Dist: dependencies>=7.7.0
14
14
  Provides-Extra: test
15
15
  Requires-Dist: pytest>=8.3.3; extra == "test"
16
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,560 @@
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
+ └── app/
94
+ ├── commands/
95
+ │ ├── __init__.py
96
+ │ ├── calc.py
97
+ │ └── valid.py
98
+ ├── configs/
99
+ │ ├── __init__.py
100
+ │ └── config.yaml
101
+ └── models/
102
+ ├── __init__.py
103
+ └── calc.py
104
+ ```
105
+
106
+ 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.
107
+
108
+ ## Crafting the Calculator Application
109
+ 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
+ ### 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. 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
+ Create app/models/calc.py with the following content:
115
+
116
+ ```python
117
+ from tiferet.models import *
118
+
119
+ class Number(ValueObject):
120
+ '''
121
+ A value object representing a numerical value in the calculator domain.
122
+ '''
123
+ value = StringType(
124
+ required=True,
125
+ regex=r'^-?\d*\.?\d*$',
126
+ metadata=dict(
127
+ description='A string representing an integer or float (e.g., "123", "-123.45", "0123", ".123", "123.").'
128
+ )
129
+ )
130
+
131
+ def is_float(self) -> bool:
132
+ '''
133
+ Check if the value is formatted as a float.
134
+
135
+ :return: True if the value contains a decimal point and valid digits, False otherwise.
136
+ '''
137
+ return '.' in self.value and self.value.strip('-.').replace('.', '').isdigit()
138
+
139
+ def format(self) -> int | float:
140
+ '''
141
+ Convert the string value to an integer or float.
142
+
143
+ :return: An integer if the value represents a whole number, otherwise a float.
144
+ '''
145
+ if self.is_float():
146
+ return float(self.value)
147
+ return int(self.value)
148
+ ```
149
+
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", "-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
+ ### 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, 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
+ #### Arithmetic Commands in commands/calc.py
156
+ Create app/commands/calc.py with the following content:
157
+
158
+ ```python
159
+ from ..commands import Command
160
+ from ..models import ModelObject
161
+ from ..models.calc import Number
162
+
163
+ class AddNumber(Command):
164
+ '''
165
+ A command to perform addition of two numbers.
166
+ '''
167
+ def execute(self, a: Number, b: Number) -> Number:
168
+ '''
169
+ Execute the addition command.
170
+
171
+ :param a: A Number object representing the first number.
172
+ :type a: Number
173
+ :param b: A Number object representing the second number.
174
+ :type b: Number
175
+ :return: A Number object representing the sum of a and b.
176
+ :rtype: Number
177
+ '''
178
+ return ModelObject.new(Number, value=str(a.format() + b.format()))
179
+
180
+ class SubtractNumber(Command):
181
+ '''
182
+ A command to perform subtraction of two numbers.
183
+ '''
184
+ def execute(self, a: Number, b: Number) -> Number:
185
+ '''
186
+ Execute the subtraction command.
187
+
188
+ :param a: A Number object representing the first number.
189
+ :type a: Number
190
+ :param b: A Number object representing the second number.
191
+ :type b: Number
192
+ :return: A Number object representing the difference of a and b.
193
+ :rtype: Number
194
+ '''
195
+ return ModelObject.new(Number, value=str(a.format() - b.format()))
196
+
197
+ class MultiplyNumber(Command):
198
+ '''
199
+ A command to perform multiplication of two numbers.
200
+ '''
201
+ def execute(self, a: Number, b: Number) -> Number:
202
+ '''
203
+ Execute the multiplication command.
204
+
205
+ :param a: A Number object representing the first number.
206
+ :type a: Number
207
+ :param b: A Number object representing the second number.
208
+ :type b: Number
209
+ :return: A Number object representing the product of a and b.
210
+ :rtype: Number
211
+ '''
212
+ return ModelObject.new(Number, value=str(a.format() * b.format()))
213
+
214
+ class DivideNumber(Command):
215
+ '''
216
+ A command to perform division of two numbers.
217
+ '''
218
+ def execute(self, a: Number, b: Number) -> Number:
219
+ '''
220
+ Execute the division command.
221
+
222
+ :param a: A Number object representing the first number.
223
+ :type a: Number
224
+ :param b: A Number object representing the second number, must be non-zero.
225
+ :type b: Number
226
+ :return: A Number object representing the quotient of a and b.
227
+ :rtype: Number
228
+ '''
229
+ self.verify(b.format() != 0, 'DIVISION_BY_ZERO')
230
+ return ModelObject.new(Number, value=str(a.format() / b.format()))
231
+
232
+ class ExponentiateNumber(Command):
233
+ '''
234
+ A command to perform exponentiation of two numbers.
235
+ '''
236
+ def execute(self, a: Number, b: Number) -> Number:
237
+ '''
238
+ Execute the exponentiation command.
239
+
240
+ :param a: A Number object representing the base number.
241
+ :type a: Number
242
+ :param b: A Number object representing the exponent.
243
+ :type b: Number
244
+ :return: A Number object representing a raised to the power of b.
245
+ :rtype: Number
246
+ '''
247
+ return ModelObject.new(Number, value=str(a.format() ** b.format()))
248
+ ```
249
+
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.
251
+
252
+ #### Validation Command in commands/valid.py
253
+ Create app/commands/valid.py with the following content:
254
+
255
+ ```python
256
+ from ..commands import Command
257
+ from ..models.calc import Number
258
+
259
+ class ValidateNumber(Command):
260
+ '''
261
+ A command to validate that a value can be a Number object.
262
+ '''
263
+ def execute(self, value: str) -> None:
264
+ '''
265
+ Validate that the input value can be used to create a Number object.
266
+
267
+ :param value: Any string value to validate.
268
+ :type value: str
269
+ :raises TiferetError: If the value cannot be a Number.
270
+ '''
271
+ try:
272
+ ModelObject.new(Number, value=str(value))
273
+ except Exception as e:
274
+ self.verify(False, 'INVALID_INPUT', value)
275
+ ```
276
+
277
+ The ValidateNumber command ensures inputs can be converted to Number objects, raising a configured error for invalid values.
278
+
279
+ ### Configuring the Application in configs/config.yaml
280
+ The calculator’s behavior is defined in app/configs/config.yaml, 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
+
282
+ Create `app/configs/config.yaml` with the following content:
283
+
284
+ ```yaml
285
+ attrs:
286
+ add_number_cmd:
287
+ module_path: app.commands.calc
288
+ class_name: AddNumber
289
+ subtract_number_cmd:
290
+ module_path: app.commands.calc
291
+ class_name: SubtractNumber
292
+ multiply_number_cmd:
293
+ module_path: app.commands.calc
294
+ class_name: MultiplyNumber
295
+ divide_number_cmd:
296
+ module_path: app.commands.calc
297
+ class_name: DivideNumber
298
+ exponentiate_number_cmd:
299
+ module_path: app.commands.calc
300
+ class_name: ExponentiateNumber
301
+ validate_number_cmd:
302
+ module_path: app.commands.valid
303
+ class_name: ValidateNumber
304
+
305
+ features:
306
+ calc.add:
307
+ name: 'Add Number'
308
+ description: 'Adds one number to another'
309
+ commands:
310
+ - attribute_id: validate_number_cmd
311
+ name: Validate `a` input
312
+ data_key: a
313
+ params:
314
+ value: $r.a
315
+ - attribute_id: validate_number_cmd
316
+ name: Validate `b` input
317
+ data_key: b
318
+ params:
319
+ value: $r.b
320
+ - attribute_id: add_number_cmd
321
+ name: Add `a` and `b`
322
+ calc.subtract:
323
+ name: 'Subtract Number'
324
+ description: 'Subtracts one number from another'
325
+ commands:
326
+ - attribute_id: validate_number_cmd
327
+ name: Validate `a` input
328
+ data_key: a
329
+ params:
330
+ value: $r.a
331
+ - attribute_id: validate_number_cmd
332
+ name: Validate `b` input
333
+ data_key: b
334
+ params:
335
+ value: $r.b
336
+ - attribute_id: subtract_number_cmd
337
+ name: Subtract `b` from `a`
338
+ calc.multiply:
339
+ name: 'Multiply Number'
340
+ description: 'Multiplies one number by another'
341
+ commands:
342
+ - attribute_id: validate_number_cmd
343
+ name: Validate `a` input
344
+ data_key: a
345
+ params:
346
+ value: $r.a
347
+ - attribute_id: validate_number_cmd
348
+ name: Validate `b` input
349
+ data_key: b
350
+ params:
351
+ value: $r.b
352
+ - attribute_id: multiply_number_cmd
353
+ name: Multiply `a` and `b`
354
+ calc.divide:
355
+ name: 'Divide Number'
356
+ description: 'Divides one number by another'
357
+ commands:
358
+ - attribute_id: validate_number_cmd
359
+ name: Validate `a` input
360
+ data_key: a
361
+ params:
362
+ value: $r.a
363
+ - attribute_id: validate_number_cmd
364
+ name: Validate `b` input
365
+ data_key: b
366
+ params:
367
+ value: $r.b
368
+ - attribute_id: divide_number_cmd
369
+ name: Divide `a` by `b`
370
+ calc.exp:
371
+ name: 'Exponentiate Number'
372
+ description: 'Raises one number to the power of another'
373
+ commands:
374
+ - attribute_id: validate_number_cmd
375
+ name: Validate `a` input
376
+ data_key: a
377
+ params:
378
+ value: $r.a
379
+ - attribute_id: validate_number_cmd
380
+ name: Validate `b` input
381
+ data_key: b
382
+ params:
383
+ value: $r.b
384
+ - attribute_id: exponentiate_number_cmd
385
+ name: Raise `a` to the power of `b`
386
+ calc.sqrt:
387
+ name: 'Square Root'
388
+ description: 'Calculates the square root of a number'
389
+ commands:
390
+ - attribute_id: validate_number_cmd
391
+ name: Validate `a` input
392
+ data_key: a
393
+ params:
394
+ value: $r.a
395
+ - attribute_id: validate_number_cmd
396
+ name: Convert `b` to number
397
+ data_key: b
398
+ params:
399
+ value: '0.5'
400
+ - attribute_id: exponentiate_number_cmd
401
+ name: Calculate square root of `a`
402
+
403
+ errors:
404
+ invalid_input:
405
+ name: Invalid Numeric Input
406
+ message:
407
+ - lang: en_US
408
+ text: 'Value {} must be a number'
409
+ - lang: es_ES
410
+ text: 'El valor {} debe ser un número'
411
+ division_by_zero:
412
+ name: Division By Zero
413
+ message:
414
+ - lang: en_US
415
+ text: 'Cannot divide by zero'
416
+ - lang: es_ES
417
+ text: 'No se puede dividir por cero'
418
+
419
+ contexts:
420
+ basic_calc:
421
+ name: Basic Calculator
422
+ description: Perform basic calculator operations
423
+ const:
424
+ container_config_file: 'app/configs/config.yaml'
425
+ feature_config_file: 'app/configs/config.yaml'
426
+ error_config_file: 'app/configs/config.yaml'
427
+ ```
428
+
429
+ attrs: Defines container attributes for dependency injection, mapping to command classes (e.g., add_number_cmd to AddNumber).
430
+
431
+
432
+ 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.
433
+
434
+ errors: Specifies error messages for invalid_input and division_by_zero, supporting en_US and es_ES for multilingual extensibility.
435
+
436
+ contexts: Defines the basic_calc application instance, linking to the configuration file for container, features, and errors.
437
+
438
+ ### Initializing and Demonstrating the Calculator in basic_calc.py
439
+ 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.
440
+ Create basic_calc.py with the following content:
441
+
442
+ ```python
443
+ from tiferet import App
444
+
445
+ # Create new app (manager) instance.
446
+ app = App(config_file='app/configs/config.yaml')
447
+
448
+ # Execute the add feature to add the values.
449
+ a = 1
450
+ b = 2
451
+ addition = app.execute_feature('basic_calc', 'calc.add', a=str(a), b=str(b))
452
+
453
+ print(f'{a} + {b} = {addition.format()}')
454
+ ```
455
+
456
+ ### Demonstrating the Calculator
457
+ To run the calculator, ensure your tiferet_app virtual environment is activated and Tiferet is installed. Execute the initializer script:
458
+ ```bash
459
+ python basic_calc
460
+ ```
461
+
462
+ ### 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.yaml, accepting command-line arguments for operations, input values, and locale selection. 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).
464
+ 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). An optional --locale flag selects the error message language (en_US or es_ES, defaulting to en_US). The script executes the specified feature in the basic_calc context, returning a Number object whose format() method yields the result as an integer or float.
465
+
466
+ Create calc_cli.py with the following content:
467
+ ```python
468
+ import argparse
469
+ from tiferet import App, TiferetError
470
+
471
+ def main():
472
+ """Parse CLI arguments and execute the calculator feature."""
473
+ parser = argparse.ArgumentParser(description="Basic Calculator CLI using Tiferet")
474
+ parser.add_argument('--config', default='app/configs/config.yaml', help='Path to config file')
475
+ parser.add_argument('--locale', default='en_US', choices=['en_US', 'es_ES'], help='Language for error messages')
476
+
477
+ subparsers = parser.add_subparsers(dest='operation', required=True, help='Calculator operation')
478
+
479
+ # Define subcommands for each feature
480
+ operations = [
481
+ ('add', 'Add two numbers', True),
482
+ ('subtract', 'Subtract one number from another', True),
483
+ ('multiply', 'Multiply two numbers', True),
484
+ ('divide', 'Divide one number by another', True),
485
+ ('exp', 'Raise one number to the power of another', True),
486
+ ('sqrt', 'Calculate the square root of a number', False),
487
+ ]
488
+
489
+ for op, help_text, needs_b in operations:
490
+ subparser = subparsers.add_parser(op, help=help_text)
491
+ subparser.add_argument('-a', required=True, help='First number')
492
+ if needs_b:
493
+ subparser.add_argument('-b', required=True, help='Second number')
494
+
495
+ args = parser.parse_args()
496
+
497
+ # Create app instance
498
+ app = App(config_file=args.config)
499
+
500
+ # Map operation to feature ID
501
+ feature_map = {
502
+ 'add': 'calc.add',
503
+ 'subtract': 'calc.subtract',
504
+ 'multiply': 'calc.multiply',
505
+ 'divide': 'calc.divide',
506
+ 'exp': 'calc.exp',
507
+ 'sqrt': 'calc.sqrt',
508
+ }
509
+ feature_id = feature_map[args.operation]
510
+
511
+ # Prepare feature parameters
512
+ params = {'a': str(args.a)}
513
+ if args.operation != 'sqrt':
514
+ params['b'] = str(args.b)
515
+
516
+ try:
517
+ # Execute feature with locale
518
+ result = app.execute_feature('basic_calc', feature_id, locale=args.locale, **params)
519
+
520
+ # Display result
521
+ if args.operation == 'sqrt':
522
+ print(f"√{args.a} = {result.format()}")
523
+ else:
524
+ op_symbol = {'add': '+', 'subtract': '-', 'multiply': '*', 'divide': '/', 'exp': '^'}[args.operation]
525
+ print(f"{args.a} {op_symbol} {args.b} = {result.format()}")
526
+ except TiferetError as e:
527
+ print(f"Error: {e.message}")
528
+ except Exception as e:
529
+ print(f"Unexpected error: {str(e)}")
530
+
531
+ if __name__ == '__main__':
532
+ main()
533
+ ```
534
+
535
+ Run the CLI with commands like:
536
+ ```bash
537
+ # Add two numbers (default en_US)
538
+ python calc_cli.py add -a 1 -b 2
539
+ # Output: 1 + 2 = 3
540
+
541
+ # Calculate square root (en_US)
542
+ python calc_cli.py sqrt -a 4
543
+ # Output: √4 = 2.0
544
+
545
+ # Invalid input (es_ES)
546
+ python calc_cli.py add -a abc -b 2 --locale es_ES
547
+ # Output: Error: El valor abc debe ser un número
548
+
549
+ # Division by zero (en_US)
550
+ python calc_cli.py divide -a 5 -b 0
551
+ # Output: Error: Cannot divide by zero
552
+ ```
553
+
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, which executes a single feature (e.g., calc.add) with hardcoded values. The CLI’s argument-driven design allows precise control over operations, with error messages tailored to the selected locale, showcasing Tiferet’s multilingual capabilities.
555
+
556
+ ## 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.
558
+
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, 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.
560
+ To continue your journey, try running additional features with calc_cli.py, experiment with new feature configurations in app/configs/config.yaml, 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.
@@ -5,11 +5,11 @@ except:
5
5
 
6
6
  config = {
7
7
  'description': 'A multi-purpose application framework embodying beauty in form.',
8
- 'author': 'Andrew Shatz',
8
+ 'author': 'Andrew Shatz, Great Strength Systems',
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-alpha.18',
12
+ 'version': '1.0.0-beta.0',
13
13
  'license': 'BSD 3',
14
14
  'install_requires': [
15
15
  'schematics>=2.1.1',
@@ -22,9 +22,12 @@ config = {
22
22
  'tiferet.commands',
23
23
  'tiferet.configs',
24
24
  'tiferet.contexts',
25
+ 'tiferet.contracts',
25
26
  'tiferet.data',
26
- 'tiferet.domain',
27
- 'tiferet.repos'
27
+ 'tiferet.handlers',
28
+ 'tiferet.models',
29
+ 'tiferet.proxies',
30
+ 'tiferet.proxies.yaml',
28
31
  ],
29
32
  'scripts': [],
30
33
  'name': 'tiferet',
@@ -0,0 +1,6 @@
1
+ # *** imports
2
+
3
+ # ** app
4
+ from .contexts.app import AppContext as App
5
+ from .commands import *
6
+ from .contracts import *
@@ -0,0 +1,6 @@
1
+ # *** imports
2
+
3
+ # ** app
4
+ from .settings import *
5
+ from .core import *
6
+ from ..models import *