tanka 0.0.1__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 (128) hide show
  1. tanka-0.0.1/.claude/rules/eo.md +387 -0
  2. tanka-0.0.1/.claude/rules/testing.md +136 -0
  3. tanka-0.0.1/.claude/rules/workflow.md +31 -0
  4. tanka-0.0.1/.claude/settings.json +5 -0
  5. tanka-0.0.1/.claude/skills/test-runner/SKILL.md +56 -0
  6. tanka-0.0.1/.flake8 +4 -0
  7. tanka-0.0.1/.github/workflows/linters.yml +25 -0
  8. tanka-0.0.1/.github/workflows/release.yml +32 -0
  9. tanka-0.0.1/.github/workflows/tests.yml +27 -0
  10. tanka-0.0.1/.gitignore +226 -0
  11. tanka-0.0.1/LICENSE +21 -0
  12. tanka-0.0.1/Makefile +31 -0
  13. tanka-0.0.1/PKG-INFO +816 -0
  14. tanka-0.0.1/README.md +771 -0
  15. tanka-0.0.1/pyproject.toml +84 -0
  16. tanka-0.0.1/src/tanka/__init__.py +205 -0
  17. tanka-0.0.1/src/tanka/abort.py +35 -0
  18. tanka-0.0.1/src/tanka/application.py +101 -0
  19. tanka-0.0.1/src/tanka/asgi.py +133 -0
  20. tanka-0.0.1/src/tanka/auth.py +83 -0
  21. tanka-0.0.1/src/tanka/body.py +158 -0
  22. tanka-0.0.1/src/tanka/catch.py +117 -0
  23. tanka-0.0.1/src/tanka/cookies.py +97 -0
  24. tanka-0.0.1/src/tanka/cors.py +126 -0
  25. tanka-0.0.1/src/tanka/endpoint.py +10 -0
  26. tanka-0.0.1/src/tanka/flash.py +96 -0
  27. tanka-0.0.1/src/tanka/headers.py +37 -0
  28. tanka-0.0.1/src/tanka/identity.py +31 -0
  29. tanka-0.0.1/src/tanka/method.py +58 -0
  30. tanka-0.0.1/src/tanka/openapi.py +212 -0
  31. tanka-0.0.1/src/tanka/request.py +74 -0
  32. tanka-0.0.1/src/tanka/response.py +99 -0
  33. tanka-0.0.1/src/tanka/routes.py +90 -0
  34. tanka-0.0.1/src/tanka/server.py +96 -0
  35. tanka-0.0.1/src/tanka/static.py +40 -0
  36. tanka-0.0.1/src/tanka/target.py +77 -0
  37. tanka-0.0.1/tests/fakes.py +62 -0
  38. tanka-0.0.1/tests/test_abort.py +35 -0
  39. tanka-0.0.1/tests/test_aborted.py +60 -0
  40. tanka-0.0.1/tests/test_alert.py +11 -0
  41. tanka-0.0.1/tests/test_all_of.py +20 -0
  42. tanka-0.0.1/tests/test_allow_credentials.py +17 -0
  43. tanka-0.0.1/tests/test_allow_headers.py +46 -0
  44. tanka-0.0.1/tests/test_allow_methods.py +46 -0
  45. tanka-0.0.1/tests/test_allow_origins.py +76 -0
  46. tanka-0.0.1/tests/test_anonymous.py +19 -0
  47. tanka-0.0.1/tests/test_any_of.py +20 -0
  48. tanka-0.0.1/tests/test_asgi.py +156 -0
  49. tanka-0.0.1/tests/test_asgi_body.py +80 -0
  50. tanka-0.0.1/tests/test_authenticated.py +46 -0
  51. tanka-0.0.1/tests/test_authorized.py +57 -0
  52. tanka-0.0.1/tests/test_catch.py +119 -0
  53. tanka-0.0.1/tests/test_code.py +19 -0
  54. tanka-0.0.1/tests/test_cookie.py +19 -0
  55. tanka-0.0.1/tests/test_cookie_path.py +11 -0
  56. tanka-0.0.1/tests/test_cookies.py +35 -0
  57. tanka-0.0.1/tests/test_core_request.py +92 -0
  58. tanka-0.0.1/tests/test_core_response.py +53 -0
  59. tanka-0.0.1/tests/test_cors.py +118 -0
  60. tanka-0.0.1/tests/test_delete.py +11 -0
  61. tanka-0.0.1/tests/test_directory.py +57 -0
  62. tanka-0.0.1/tests/test_document.py +42 -0
  63. tanka-0.0.1/tests/test_domain.py +11 -0
  64. tanka-0.0.1/tests/test_empty.py +19 -0
  65. tanka-0.0.1/tests/test_error_page.py +28 -0
  66. tanka-0.0.1/tests/test_every.py +14 -0
  67. tanka-0.0.1/tests/test_expose_headers.py +39 -0
  68. tanka-0.0.1/tests/test_failsafe.py +47 -0
  69. tanka-0.0.1/tests/test_failure.py +11 -0
  70. tanka-0.0.1/tests/test_file.py +74 -0
  71. tanka-0.0.1/tests/test_flash.py +19 -0
  72. tanka-0.0.1/tests/test_flashes.py +52 -0
  73. tanka-0.0.1/tests/test_forget_cookie.py +19 -0
  74. tanka-0.0.1/tests/test_get.py +11 -0
  75. tanka-0.0.1/tests/test_head.py +11 -0
  76. tanka-0.0.1/tests/test_headers.py +76 -0
  77. tanka-0.0.1/tests/test_html.py +19 -0
  78. tanka-0.0.1/tests/test_http_only.py +11 -0
  79. tanka-0.0.1/tests/test_hypercorn.py +50 -0
  80. tanka-0.0.1/tests/test_indifferent.py +23 -0
  81. tanka-0.0.1/tests/test_json.py +21 -0
  82. tanka-0.0.1/tests/test_lifetime.py +11 -0
  83. tanka-0.0.1/tests/test_listed.py +19 -0
  84. tanka-0.0.1/tests/test_logging.py +15 -0
  85. tanka-0.0.1/tests/test_max_age.py +37 -0
  86. tanka-0.0.1/tests/test_methods.py +19 -0
  87. tanka-0.0.1/tests/test_mount.py +58 -0
  88. tanka-0.0.1/tests/test_named.py +11 -0
  89. tanka-0.0.1/tests/test_notice.py +11 -0
  90. tanka-0.0.1/tests/test_on.py +59 -0
  91. tanka-0.0.1/tests/test_once.py +17 -0
  92. tanka-0.0.1/tests/test_open_api.py +281 -0
  93. tanka-0.0.1/tests/test_options.py +11 -0
  94. tanka-0.0.1/tests/test_package.py +19 -0
  95. tanka-0.0.1/tests/test_patch.py +11 -0
  96. tanka-0.0.1/tests/test_path.py +45 -0
  97. tanka-0.0.1/tests/test_post.py +11 -0
  98. tanka-0.0.1/tests/test_preflight.py +62 -0
  99. tanka-0.0.1/tests/test_principal.py +19 -0
  100. tanka-0.0.1/tests/test_put.py +11 -0
  101. tanka-0.0.1/tests/test_query.py +43 -0
  102. tanka-0.0.1/tests/test_range.py +22 -0
  103. tanka-0.0.1/tests/test_raw.py +27 -0
  104. tanka-0.0.1/tests/test_redirect.py +35 -0
  105. tanka-0.0.1/tests/test_reload.py +27 -0
  106. tanka-0.0.1/tests/test_request.py +110 -0
  107. tanka-0.0.1/tests/test_response.py +55 -0
  108. tanka-0.0.1/tests/test_role.py +28 -0
  109. tanka-0.0.1/tests/test_roles.py +28 -0
  110. tanka-0.0.1/tests/test_route.py +132 -0
  111. tanka-0.0.1/tests/test_routes.py +68 -0
  112. tanka-0.0.1/tests/test_same_site.py +11 -0
  113. tanka-0.0.1/tests/test_secure.py +11 -0
  114. tanka-0.0.1/tests/test_silence.py +11 -0
  115. tanka-0.0.1/tests/test_smart.py +53 -0
  116. tanka-0.0.1/tests/test_static.py +66 -0
  117. tanka-0.0.1/tests/test_status.py +29 -0
  118. tanka-0.0.1/tests/test_stream.py +33 -0
  119. tanka-0.0.1/tests/test_success.py +11 -0
  120. tanka-0.0.1/tests/test_tanka.py +99 -0
  121. tanka-0.0.1/tests/test_target.py +35 -0
  122. tanka-0.0.1/tests/test_text.py +27 -0
  123. tanka-0.0.1/tests/test_uvicorn.py +50 -0
  124. tanka-0.0.1/tests/test_verb.py +11 -0
  125. tanka-0.0.1/tests/test_with_cookie.py +46 -0
  126. tanka-0.0.1/tests/test_with_flash.py +46 -0
  127. tanka-0.0.1/tests/test_with_headers.py +36 -0
  128. tanka-0.0.1/uv.lock +1625 -0
@@ -0,0 +1,387 @@
1
+ # Elegant Objects Coding Rules
2
+
3
+ ---
4
+ ## 1. General
5
+ ### 1.1 Language
6
+ - Always use python type hints.
7
+ ---
8
+ ## 2. Naming
9
+ ### 2.1 Class Names
10
+ - Never use class names ending in -er, -or, or -Utils (exceptions: User, Computer).
11
+ - Naming a class based on what its objects do is incorrect.
12
+ - Classes should be named based on what they are, not what they do.
13
+ - Think about what the objects will encapsulate and choose a name that represents that conceptual group.
14
+ ### 2.2 Method Names
15
+ - A builder is a method that constructs or returns something.
16
+ - A builder never returns None, and its name must always be a noun (optionally with an adjective).
17
+
18
+ ```python
19
+ def pow(self, base: int, power: int) -> int:
20
+ pass
21
+
22
+
23
+ def speed(self) -> float:
24
+ pass
25
+
26
+
27
+ def employee(self, id: int) -> Employee:
28
+ pass
29
+
30
+
31
+ def parsed_cell(self, x: int, y: int) -> str:
32
+ pass
33
+ ```
34
+
35
+ - A manipulator is a method that modifies the real-world entity represented by an object.
36
+ - A manipulator always returns None (implicitly), and its name must always be a verb (optionally with an adverb).
37
+
38
+ ```python
39
+ def save(self, content: str):
40
+ pass
41
+
42
+
43
+ def put(self, key: str, value: float):
44
+ pass
45
+
46
+
47
+ def remove(self, emp: Employee):
48
+ pass
49
+
50
+
51
+ def quickly_print(self, id: int):
52
+ pass
53
+ ```
54
+
55
+ - If a builder returns a Boolean value, its name should be an adjective.
56
+ - The prefix is_ is redundant and should not be used, but temporarily adding it can help ensure the name sounds correct.
57
+
58
+ ```python
59
+ def empty(self) -> bool:
60
+ pass
61
+
62
+
63
+ def readable(self) -> bool:
64
+ pass
65
+
66
+
67
+ def negative(self) -> bool:
68
+ pass
69
+ ```
70
+
71
+ - equals should be renamed to equal_to.
72
+ - exists should be renamed to present.
73
+ ### 2.3 Variable Names
74
+ - Do not use compound names anywhere in the code.
75
+ - Every variable must have a name consisting of a single noun.
76
+ - Exceptions are allowed only when a single noun would lose its meaning without an adjective, for example: time_zone, side_effect, MicroService, ChangingRoom, washing_machine, bus_stop, laughing_ga.
77
+ ---
78
+ ## 3. Constructors
79
+ ### 3.1 Constructor Design
80
+ - Prefer having one primary constructor.
81
+ - The more constructors a class has, the more flexibility it gives to clients using the class.
82
+ - Keep constructors code-free: they should only contain assignment statements.
83
+ - Allow only assignments in one primary constructor, delegating from secondaries.
84
+ - Use plum-dispatch python library for method overloading.
85
+
86
+ ```python
87
+ from plum import dispatch
88
+
89
+
90
+ class Cash:
91
+ @dispatch
92
+ def __init__(self, value: float):
93
+ self.__init__(int(value))
94
+
95
+ @dispatch
96
+ def __init__(self, value: str):
97
+ self.__init__(int(value))
98
+
99
+ @dispatch
100
+ def __init__(self, value: int):
101
+ self.dollars = value
102
+ ```
103
+ ---
104
+ ## 4. Objects and Encapsulation
105
+ ### 4.1 Attributes
106
+ - All encapsulated objects (attributes) are part of an object's identity.
107
+ - An object should encapsulate four or fewer attributes.
108
+ - If more attributes are required, they should be grouped into other objects.
109
+ - Together, these objects should form a structured tree of objects.
110
+ - An object that encapsulates nothing should not exist.
111
+ - Never create getters and setters.
112
+ - Never add an attribute or method to an object at runtime.
113
+ ---
114
+ ## 5. Interfaces and Methods
115
+ ### 5.1 Interfaces
116
+ - Always use interfaces.
117
+ - Ensure that every public method in a class implements an interface.
118
+ - Keep interfaces minimal.
119
+ - Add convenience behavior through smart wrappers.
120
+
121
+ ```python
122
+ # Instead of:
123
+ class Exchange(ABC):
124
+ @abstractmethod
125
+ def rate(
126
+ self,
127
+ target: str,
128
+ source: str = "USD",
129
+ ) -> float:
130
+ pass
131
+
132
+
133
+ # Use:
134
+ class Exchange(ABC):
135
+ @abstractmethod
136
+ def rate(
137
+ self,
138
+ target: str,
139
+ source: str,
140
+ ) -> float:
141
+ pass
142
+
143
+ class Smart:
144
+ def __init__(self, e: "Exchange"):
145
+ self.origin = e
146
+
147
+ def to_usd(
148
+ self,
149
+ source: str,
150
+ ) -> float:
151
+ return self.origin.rate(
152
+ source,
153
+ "USD",
154
+ )
155
+
156
+
157
+ # Usage:
158
+ rate = Exchange.Smart(NYSE()).to_usd("EUR")
159
+ ```
160
+ ### 5.2 Methods
161
+ - The more methods a class exposes, the harder it becomes to use correctly.
162
+ - The number of public methods in a class should be five or fewer.
163
+ - Never create static methods.
164
+ - Long methods are forbidden.
165
+ - Methods with multiple return statements are forbidden.
166
+ ---
167
+ ## 6. Immutability
168
+ ### 6.1 Immutable Objects
169
+ - All classes should be immutable.
170
+ - If modification is needed, create a new object instead.
171
+ ---
172
+ ## 7. Testing
173
+ ### 7.1 Unit Tests
174
+ - Write unit tests instead of docstrings.
175
+ - Do not use mocks.
176
+ - Instead mocks, create fake classes that implement interfaces and use them in unit tests.
177
+ ---
178
+ ## 8. Constants
179
+ ### 8.1 Avoiding Constants
180
+ - Do not use public constants.
181
+ - Instead, create a class that encapsulates the semantic meaning of the constant.
182
+
183
+ ```python
184
+ # Wrong
185
+ class Constants:
186
+ EOF = "\n"
187
+
188
+
189
+ # Correct
190
+ class EOLString:
191
+ def __init__(self, src: str):
192
+ self.origin = src
193
+
194
+ def __str__(self) -> str:
195
+ return self.origin + "\n"
196
+ ```
197
+
198
+ - Stay away from class-level constants.
199
+
200
+ ```python
201
+ # Bad
202
+ class Book:
203
+ BOOK_NOT_FOUND = "book not found"
204
+
205
+ def print(self):
206
+ if {book not found}:
207
+ raise Exception(
208
+ self.BOOK_NOT_FOUND
209
+ )
210
+
211
+ def sell(self):
212
+ if {book not found}:
213
+ raise Exception(
214
+ self.BOOK_NOT_FOUND
215
+ )
216
+
217
+ # Good 1
218
+ class Book:
219
+ def print(self):
220
+ if {book not found}:
221
+ raise Exception(
222
+ "book not found, can’t print it"
223
+ )
224
+
225
+ def sell(self):
226
+ if {book not found}:
227
+ raise Exception(
228
+ "book not found, can’t sell it"
229
+ )
230
+
231
+ # Good 2
232
+ class Book:
233
+ def print(self):
234
+ x = self._find()
235
+
236
+ def sell(self):
237
+ x = self._find()
238
+
239
+ def _find(self) -> X:
240
+ if {book not found}:
241
+ raise Exception(
242
+ "book not found"
243
+ )
244
+ return "the book found"
245
+ ```
246
+ ---
247
+ ## 9. Exceptions and Fail-Fast
248
+ ### 9.1 Fail-Fast
249
+ - Always follow the fail-fast approach.
250
+ - Throw an exception in any suspicious situation.
251
+ - When something is not found, do one of the following: throw an exception; return a collection (possibly empty); return a Null Object.
252
+
253
+ ```python
254
+ class NullUser(User):
255
+ def __init__(self, name: str):
256
+ self.label = name
257
+
258
+ def name(self) -> str:
259
+ return self.label
260
+
261
+ def boost(self, salary: Cash):
262
+ raise IllegalStateException("You can’t raise my salary")
263
+ ```
264
+
265
+ - Never accept None as an argument.
266
+ - Instead of None, create a Null Object implementation.
267
+
268
+ ```python
269
+ class Mask(ABC):
270
+ @abstractmethod
271
+ def matches(self, f: File) -> bool:
272
+ pass
273
+
274
+
275
+ class AnyFile(Mask):
276
+ def matches(self, f: File) -> bool:
277
+ return True
278
+ ```
279
+ ### 9.2 Exception Handling
280
+ - Every except statement must have a very strong reason to exist.
281
+ - Always Chain Exceptions.
282
+
283
+ ```python
284
+ def length(self, f: File) -> int:
285
+ try:
286
+ return os.path.getsize(PathOf(f))
287
+ except FileNotFoundError as e:
288
+ raise Exception("Can’t calculate file length.") from e
289
+ ```
290
+
291
+ - Recover only once, at the highest level (the application entry point).
292
+ - All raised exceptions should be of the same type (Exception).
293
+ ### 9.3 Validation
294
+ - Input validation and assertions should be implemented through decorators (wrappers), not inside core logic.
295
+
296
+ ```python
297
+ class Day(ABC):
298
+ @abstractmethod
299
+ def distance_to(self, end: "Day") -> int:
300
+ pass
301
+
302
+
303
+ class JdkDay(Day):
304
+ def __init__(self, d: Date):
305
+ self.date = d
306
+
307
+ def distance_to(self, end: "Day") -> int:
308
+ # assertion validation happens externally
309
+ return LongAsInteger(end.date - self.date)
310
+
311
+
312
+ # Input validation decorator
313
+ class StrictDay(Day):
314
+ def __init__(self, d: Day):
315
+ self.origin = d
316
+
317
+ def distance_to(self, end: Day) -> int:
318
+ if end.compare_to(self) < 0:
319
+ raise Exception(f"Start {self} must be earlier than end {end}")
320
+ return self.origin.distance_to(end)
321
+
322
+
323
+ # Usage
324
+ day = StrictDay(JdkDay(Date()))
325
+ ```
326
+
327
+ - Use Aspect-Oriented Programming to move supplementary mechanisms (like retries, logging, etc.) outside of core classes.
328
+
329
+ ```python
330
+ from tenacity import retry, stop_after_attempt
331
+
332
+
333
+ class WebPage:
334
+ @retry(stop=stop_after_attempt(3))
335
+ def content(self) -> str:
336
+ # fetch web page content
337
+ pass
338
+ ```
339
+ ---
340
+ ## 10. Composition over Inheritance
341
+ ### 10.1 Class Structure
342
+ - A class should be final (all methods implemented) or abstract (no methods implemented, interface only).
343
+ - There is no place for inheritance.
344
+ ### 10.2 Composition and Decorators
345
+ - Prefer composable decorators to build behavior step by step.
346
+
347
+ ```python
348
+ names = Sorted(
349
+ Unique(
350
+ Capitalized(
351
+ FileNames(
352
+ Directory(
353
+ "/var/users/*.xml",
354
+ ),
355
+ "([^.]+)\\.xml",
356
+ )
357
+ )
358
+ )
359
+ )
360
+ ```
361
+
362
+ - Whenever you need to add new functionality to an existing class, create a new class or decorate an existing one.
363
+ - Modifying the original class should always be the last resort.
364
+ - Never use dependency injection.
365
+ - Use decorators (wrappers) for access control.
366
+
367
+ ```python
368
+ car = SecureCar(Car("Mercedes-Benz SL63"), "admin")
369
+ ```
370
+
371
+ - Use object composition instead of the MVC pattern.
372
+
373
+ ```python
374
+ # Wrong
375
+ class Controller:
376
+ def index(self) -> str:
377
+ title = Model().title
378
+ view = View()
379
+ view.title = title
380
+ return view.render_html()
381
+
382
+
383
+ # Good
384
+ Application(HttpGetBook(HtmlBook(MySQLBook("Elegant Objects")))).run()
385
+ ```
386
+ ### 10.3 Forbidden Patterns
387
+ - Never use the Singleton pattern.
@@ -0,0 +1,136 @@
1
+ # Testing Rules
2
+
3
+ ---
4
+
5
+ ## 1. Test Design
6
+
7
+ ### 1.1 Logic-Free Tests
8
+ - Unit tests must be logic-free: they must not implement any algorithms.
9
+ - Aim for one-statement tests.
10
+
11
+ ### 1.2 Assertions
12
+ - Each test must contain exactly one assertion.
13
+ - Each test must contain at least one assertion.
14
+ - The last statement in every test must be an assertion.
15
+ - Do not assert on the absence of failure.
16
+ - Never assert on side effects.
17
+ - Be creative with assertions.
18
+ - Use the PyHamcrest library to achieve this.
19
+
20
+ ### 1.3 Test Inputs
21
+ - Whenever possible, tests should use irregular, boundary, or strange inputs to provoke hidden issues.
22
+ - Every test must use its own literals, numbers, and input values.
23
+ - Do not reuse inputs across tests, as this may falsely suggest shared meaning.
24
+ - Always write descriptive failure messages.
25
+
26
+ ### 1.4 Test Isolation
27
+ - Test methods must share absolutely no fixtures with other test methods.
28
+
29
+ ---
30
+
31
+ ## 2. Naming and Documentation
32
+
33
+ ### 2.1 Test Naming
34
+ - Test names must be complete English phrases that would start with "it" if written fully. Example: (it) builds_empty_png_image
35
+ - "it" refers to the object under test, not the test itself.
36
+
37
+ ### 2.2 Documentation
38
+ - Tests must not contain documentation comments.
39
+
40
+ ---
41
+
42
+ ## 3. Scope and Boundaries
43
+
44
+ ### 3.1 What to Test
45
+ - Never write tests for private or protected methods.
46
+ - Avoid overtesting.
47
+ - Tests must never be the reason for expanding an object's public interface.
48
+ - Avoid testing abstract classes directly.
49
+
50
+ ### 3.2 Feature Class Correspondence
51
+ - Each feature class must have exactly one corresponding test module.
52
+ - Feature classes should trust each other and only guard against invalid end-user input.
53
+
54
+ ---
55
+
56
+ ## 4. Resources and Cleanup
57
+
58
+ ### 4.1 System Resources
59
+ - Tests must close all system resources when they finish.
60
+ - Tests that require resources must begin with cleanup.
61
+
62
+ ### 4.2 File Handling
63
+ - If a test works with files, it must create its own temporary directory and store all files there.
64
+ - Do not mock the file system.
65
+ - Place all temporary files inside a top-level tmp/ directory in the repository.
66
+
67
+ ---
68
+
69
+ ## 5. Fakes and Logging
70
+
71
+ ### 5.1 Fakes Over Mocks
72
+ - Do not use mocks. Use fake implementations instead.
73
+
74
+ ### 5.2 Logging
75
+ - Logging should be disabled during tests whenever possible.
76
+ - If logging is part of the specification, test it using fake classes that capture messages instead of printing them.
77
+
78
+ ---
79
+
80
+ ## 6. Test Patterns
81
+
82
+ ### 6.1 Parametrized Tests
83
+ - If multiple inputs share the same scenario, use parametrized tests.
84
+
85
+ ### 6.2 Thread Safety
86
+ - If a class uses threads or claims thread safety, there must be tests confirming its thread safety.
87
+
88
+ ### 6.3 Flaky Tests
89
+ - Use pytest-repeat to run flaky tests multiple times when needed.
90
+
91
+ ### 6.4 Online Tests
92
+ - Mark all tests requiring internet access with: pytest.mark.online
93
+
94
+ ---
95
+
96
+ ## 7. Test-Driven Development
97
+
98
+ ### 7.1 Bug Fixes
99
+ - Before fixing a bug, reproduce it with a failing test case.
100
+
101
+ ### 7.2 New Features
102
+ - Before adding a new feature, there must be a failing test first.
103
+
104
+ ---
105
+
106
+ ## 8. Test Execution
107
+
108
+ ### 8.1 Performance
109
+ - Use pytest-fail-slow to automatically fail tests that run too slowly.
110
+
111
+ ### 8.2 Randomization
112
+ - Execute tests in random order and print the randomization seed to the console.
113
+
114
+ ---
115
+
116
+ ## 9. Test Categories
117
+
118
+ ### 9.1 Fast Tests (Unit)
119
+ - Minimal execution time.
120
+
121
+ ### 9.2 Deep Tests (Integration)
122
+ - Realistic live scenarios.
123
+ - If deep tests require external dependencies (databases, remote services, etc.), use Testcontainers.
124
+
125
+ ---
126
+
127
+ ## 10. Quality Tools
128
+
129
+ ### 10.1 Grammar Checking
130
+ - Use language-tool-python to check English grammar in string literals and exception messages.
131
+
132
+ ### 10.2 Property-Based Testing
133
+ - Use Python Hypothesis for property-based testing.
134
+
135
+ ### 10.3 Mutation Testing
136
+ - Use mutmut for mutation testing.
@@ -0,0 +1,31 @@
1
+ # Be Quality Dogmatic
2
+
3
+ Keep chat replies and your answers short.
4
+
5
+ ## Discipline
6
+
7
+ Write concise, direct, human prose.
8
+ Push back on technical mistakes, deferring to users on vision and architecture.
9
+ Stay on scope, refactoring only what tasks require.
10
+ Design top-down, whole before parts, composition before ingredients.
11
+
12
+ ## Workflow
13
+
14
+ Pull from Git before changes, push back only with explicit user permission.
15
+ Name each branch after its GitHub issue integer, asking when unsure.
16
+ Always practice TDD (test-driven development), reproducing bugs before fixing.
17
+ Invest in unit tests until bugs reproduce, trusting intuition over debugging.
18
+ Log extensively when problems stay hard.
19
+
20
+ ## Changes
21
+
22
+ Keep changes focused and minimal.
23
+ Flag smells and refactoring, suggesting issues rather than fixing silently.
24
+ Fix style violations in code, trusting checkers rather than suppressing them.
25
+
26
+ ## Runtime
27
+
28
+ Disable logging in tests.
29
+ Bound every wait with timeouts.
30
+ Test concurrency, retrying flaky blocks.
31
+ Assume no Internet, using ephemeral ports.
@@ -0,0 +1,5 @@
1
+ {
2
+ "env": {
3
+ "CLAUDE_CODE_USE_POWERSHELL_TOOL": "0"
4
+ }
5
+ }
@@ -0,0 +1,56 @@
1
+ ---
2
+ name: "test-runner"
3
+ description: "Use this skill when application code has been modified (*.py)"
4
+ ---
5
+
6
+ # Test Runner
7
+
8
+ **Tier:** POWERFUL
9
+ **Category:** Testing
10
+ **Domain:** Unit Testing / Quality Assurance
11
+
12
+ ---
13
+
14
+ ## Overview
15
+
16
+ This skill provides steps on how to run tests and linters after application code has been changed.
17
+ It shows how unit tests run process is organized in project and how properly work with test results.
18
+
19
+ ---
20
+
21
+ ## When to use
22
+
23
+ - New application code has been introduced (*.py)
24
+ - Application code has been changed (*.py)
25
+
26
+ ---
27
+
28
+ ## How to run tests
29
+
30
+ To run unit tests:
31
+ ```bash
32
+ make unit
33
+ ```
34
+
35
+ ## How to run linters:
36
+
37
+ To run black:
38
+ ```bash
39
+ make black
40
+ ```
41
+ To run flake8:
42
+ ```bash
43
+ make flake8
44
+ ```
45
+ To run ruff:
46
+ ```bash
47
+ make ruff
48
+ ```
49
+
50
+ ---
51
+
52
+ ## Important rules
53
+
54
+ - Test coverage should be >= 90.
55
+ - If test coverage is not enough, you should write additional tests to achieve desired state.
56
+ - If linters fail and provide changes to implement, then those changes should be implemented.
tanka-0.0.1/.flake8 ADDED
@@ -0,0 +1,4 @@
1
+ [flake8]
2
+ max-line-length = 79
3
+ extend-ignore = E203, E704, W503, F811
4
+ exclude = .venv, .git, __pycache__, build, dist, tmp
@@ -0,0 +1,25 @@
1
+ name: linters
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - "**"
7
+ pull_request:
8
+ workflow_call:
9
+
10
+ jobs:
11
+ linters:
12
+ runs-on: ubuntu-latest
13
+ steps:
14
+ - uses: actions/checkout@v7
15
+ - uses: astral-sh/setup-uv@v10.0.1
16
+ with:
17
+ python-version: "3.11"
18
+ - name: Install dependencies
19
+ run: uv sync --locked
20
+ - name: black
21
+ run: make black
22
+ - name: flake8
23
+ run: make flake8
24
+ - name: ruff
25
+ run: make ruff
@@ -0,0 +1,32 @@
1
+ name: release
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+
8
+ jobs:
9
+ tests:
10
+ uses: ./.github/workflows/tests.yml
11
+
12
+ linters:
13
+ uses: ./.github/workflows/linters.yml
14
+
15
+ publish:
16
+ needs:
17
+ - tests
18
+ - linters
19
+ runs-on: ubuntu-latest
20
+ environment: pypi
21
+ permissions:
22
+ id-token: write
23
+ contents: read
24
+ steps:
25
+ - uses: actions/checkout@v7
26
+ with:
27
+ fetch-depth: 0
28
+ - uses: astral-sh/setup-uv@v10.0.1
29
+ - name: Build distribution
30
+ run: uv build
31
+ - name: Publish to PyPI
32
+ run: uv publish --trusted-publishing always
@@ -0,0 +1,27 @@
1
+ name: tests
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - "**"
7
+ pull_request:
8
+ workflow_call:
9
+
10
+ jobs:
11
+ tests:
12
+ runs-on: ubuntu-latest
13
+ strategy:
14
+ fail-fast: false
15
+ matrix:
16
+ python: ["3.11", "3.12", "3.13", "3.14"]
17
+ steps:
18
+ - uses: actions/checkout@v7
19
+ - uses: astral-sh/setup-uv@v10.0.1
20
+ with:
21
+ python-version: ${{ matrix.python }}
22
+ - name: Install dependencies
23
+ run: uv sync --locked
24
+ - name: Unit tests
25
+ run: make unit
26
+ - name: Deep tests
27
+ run: make deep