thailint 0.15.6__py3-none-any.whl → 0.16.0__py3-none-any.whl

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.
@@ -0,0 +1,254 @@
1
+ """
2
+ Purpose: Lint rule for detecting conditional verbose logging anti-patterns
3
+
4
+ Scope: Detection of if verbose: logger.*() patterns in Python code
5
+
6
+ Overview: Implements the ConditionalVerboseRule that detects logging calls conditionally guarded
7
+ by verbose flags. This is an anti-pattern because logging levels should be configured through
8
+ the logging framework (e.g., logger.setLevel(logging.DEBUG)) rather than through code
9
+ conditionals. The rule reports violations with suggestions to remove the conditional and
10
+ configure logging levels properly. Only applies to Python files as this pattern is specific
11
+ to Python logging practices.
12
+
13
+ Dependencies: BaseLintContext and BaseLintRule from core, ast module, conditional_verbose_analyzer
14
+
15
+ Exports: ConditionalVerboseRule class implementing BaseLintRule interface
16
+
17
+ Interfaces: check(context) -> list[Violation] for rule validation, standard rule properties
18
+ (rule_id, rule_name, description)
19
+
20
+ Implementation: AST-based analysis using ConditionalVerboseAnalyzer for pattern detection
21
+ """
22
+
23
+ import ast
24
+ from pathlib import Path
25
+
26
+ from src.core.base import BaseLintContext, BaseLintRule
27
+ from src.core.constants import Language
28
+ from src.core.linter_utils import has_file_content, load_linter_config
29
+ from src.core.types import Violation
30
+ from src.core.violation_utils import get_violation_line, has_python_noqa
31
+ from src.linter_config.ignore import get_ignore_parser
32
+
33
+ from .conditional_verbose_analyzer import ConditionalVerboseAnalyzer
34
+ from .config import PrintStatementConfig
35
+
36
+
37
+ class ConditionalVerboseRule(BaseLintRule):
38
+ """Detects conditional verbose logging patterns that should use log level configuration."""
39
+
40
+ def __init__(self) -> None:
41
+ """Initialize the conditional verbose rule."""
42
+ self._ignore_parser = get_ignore_parser()
43
+
44
+ @property
45
+ def rule_id(self) -> str:
46
+ """Unique identifier for this rule."""
47
+ return "improper-logging.conditional-verbose"
48
+
49
+ @property
50
+ def rule_name(self) -> str:
51
+ """Human-readable name for this rule."""
52
+ return "Improper Logging - Conditional Verbose"
53
+
54
+ @property
55
+ def description(self) -> str:
56
+ """Description of what this rule checks."""
57
+ return "Conditional verbose logging should use log level configuration instead"
58
+
59
+ def check(self, context: BaseLintContext) -> list[Violation]:
60
+ """Check for conditional verbose logging violations.
61
+
62
+ Only applies to Python files, as this pattern is Python-specific.
63
+
64
+ Args:
65
+ context: Lint context with file information
66
+
67
+ Returns:
68
+ List of violations found
69
+ """
70
+ if not self._should_analyze(context):
71
+ return []
72
+
73
+ tree = self._parse_python_code(context.file_content)
74
+ if tree is None:
75
+ return []
76
+
77
+ analyzer = ConditionalVerboseAnalyzer()
78
+ conditional_calls = analyzer.find_conditional_verbose_calls(tree)
79
+
80
+ return self._collect_violations(conditional_calls, context)
81
+
82
+ def _should_analyze(self, context: BaseLintContext) -> bool:
83
+ """Check if this file should be analyzed."""
84
+ if not has_file_content(context):
85
+ return False
86
+ if context.language != Language.PYTHON:
87
+ return False
88
+ config = self._load_config(context)
89
+ if not config.enabled:
90
+ return False
91
+ return not self._is_file_ignored(context, config)
92
+
93
+ def _load_config(self, context: BaseLintContext) -> PrintStatementConfig:
94
+ """Load configuration from context.
95
+
96
+ Uses the same config as print-statements linter for consistency.
97
+
98
+ Args:
99
+ context: Lint context
100
+
101
+ Returns:
102
+ PrintStatementConfig instance
103
+ """
104
+ test_config = self._try_load_test_config(context)
105
+ if test_config is not None:
106
+ return test_config
107
+
108
+ prod_config = self._try_load_production_config(context)
109
+ if prod_config is not None:
110
+ return prod_config
111
+
112
+ return PrintStatementConfig()
113
+
114
+ def _try_load_test_config(self, context: BaseLintContext) -> PrintStatementConfig | None:
115
+ """Try to load test-style configuration."""
116
+ if not hasattr(context, "config"):
117
+ return None
118
+ config_attr = context.config
119
+ if config_attr is None or not isinstance(config_attr, dict):
120
+ return None
121
+ return PrintStatementConfig.from_dict(config_attr, context.language)
122
+
123
+ def _try_load_production_config(self, context: BaseLintContext) -> PrintStatementConfig | None:
124
+ """Try to load production configuration."""
125
+ if not hasattr(context, "metadata") or not isinstance(context.metadata, dict):
126
+ return None
127
+
128
+ metadata = context.metadata
129
+ config_keys = ("print_statements", "print-statements", "improper-logging")
130
+
131
+ for key in config_keys:
132
+ if key in metadata:
133
+ return load_linter_config(context, key, PrintStatementConfig)
134
+
135
+ return None
136
+
137
+ def _is_file_ignored(self, context: BaseLintContext, config: PrintStatementConfig) -> bool:
138
+ """Check if file matches ignore patterns."""
139
+ if not config.ignore:
140
+ return False
141
+ if not context.file_path:
142
+ return False
143
+
144
+ file_path = Path(context.file_path)
145
+ return any(self._matches_pattern(file_path, pattern) for pattern in config.ignore)
146
+
147
+ def _matches_pattern(self, file_path: Path, pattern: str) -> bool:
148
+ """Check if file path matches a glob pattern."""
149
+ if file_path.match(pattern):
150
+ return True
151
+ if pattern in str(file_path):
152
+ return True
153
+ return False
154
+
155
+ def _parse_python_code(self, code: str | None) -> ast.AST | None:
156
+ """Parse Python code into AST."""
157
+ try:
158
+ return ast.parse(code or "")
159
+ except SyntaxError:
160
+ return None
161
+
162
+ def _collect_violations(
163
+ self,
164
+ conditional_calls: list[tuple[ast.If, ast.Call, str, int]],
165
+ context: BaseLintContext,
166
+ ) -> list[Violation]:
167
+ """Collect violations from conditional verbose logging patterns.
168
+
169
+ Args:
170
+ conditional_calls: List of (if_node, call_node, method_name, line_number) tuples
171
+ context: Lint context
172
+
173
+ Returns:
174
+ List of violations
175
+ """
176
+ violations = []
177
+ for _if_node, _call_node, method_name, line_number in conditional_calls:
178
+ violation = self._create_violation(method_name, line_number, context)
179
+ if not self._should_ignore(violation, context):
180
+ violations.append(violation)
181
+ return violations
182
+
183
+ def _create_violation(
184
+ self,
185
+ method_name: str,
186
+ line: int,
187
+ context: BaseLintContext,
188
+ ) -> Violation:
189
+ """Create a violation for a conditional verbose logging pattern.
190
+
191
+ Args:
192
+ method_name: The logger method name (debug, info, etc.)
193
+ line: Line number where the violation occurs
194
+ context: Lint context
195
+
196
+ Returns:
197
+ Violation object with details about the pattern
198
+ """
199
+ message = f"Conditional verbose check around logger.{method_name}() should be removed"
200
+ suggestion = (
201
+ "Remove the 'if verbose:' condition and configure logging level instead. "
202
+ "Use logger.setLevel(logging.DEBUG) to control verbosity."
203
+ )
204
+
205
+ return Violation(
206
+ rule_id=self.rule_id,
207
+ file_path=str(context.file_path) if context.file_path else "",
208
+ line=line,
209
+ column=0,
210
+ message=message,
211
+ suggestion=suggestion,
212
+ )
213
+
214
+ def _should_ignore(self, violation: Violation, context: BaseLintContext) -> bool:
215
+ """Check if violation should be ignored based on inline directives.
216
+
217
+ Args:
218
+ violation: Violation to check
219
+ context: Lint context with file content
220
+
221
+ Returns:
222
+ True if violation should be ignored
223
+ """
224
+ if self._ignore_parser.should_ignore_violation(violation, context.file_content or ""):
225
+ return True
226
+ return self._check_generic_ignore(violation, context)
227
+
228
+ def _check_generic_ignore(self, violation: Violation, context: BaseLintContext) -> bool:
229
+ """Check for generic ignore directives.
230
+
231
+ Args:
232
+ violation: Violation to check
233
+ context: Lint context
234
+
235
+ Returns:
236
+ True if line has generic ignore directive
237
+ """
238
+ line_text = get_violation_line(violation, context)
239
+ if line_text is None:
240
+ return False
241
+ return self._has_generic_ignore_directive(line_text)
242
+
243
+ def _has_generic_ignore_directive(self, line_text: str) -> bool:
244
+ """Check if line has generic ignore directive."""
245
+ if self._has_generic_thailint_ignore(line_text):
246
+ return True
247
+ return has_python_noqa(line_text)
248
+
249
+ def _has_generic_thailint_ignore(self, line_text: str) -> bool:
250
+ """Check for generic thailint: ignore (no brackets)."""
251
+ if "# thailint: ignore" not in line_text:
252
+ return False
253
+ after_ignore = line_text.split("# thailint: ignore")[1].split("#")[0]
254
+ return "[" not in after_ignore
@@ -54,12 +54,12 @@ class PrintStatementRule(MultiLanguageLintRule): # thailint: ignore[srp]
54
54
  @property
55
55
  def rule_id(self) -> str:
56
56
  """Unique identifier for this rule."""
57
- return "print-statements.detected"
57
+ return "improper-logging.print-statement"
58
58
 
59
59
  @property
60
60
  def rule_name(self) -> str:
61
61
  """Human-readable name for this rule."""
62
- return "Print Statements"
62
+ return "Improper Logging - Print Statement"
63
63
 
64
64
  @property
65
65
  def description(self) -> str:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: thailint
3
- Version: 0.15.6
3
+ Version: 0.16.0
4
4
  Summary: The AI Linter - Enterprise-grade linting and governance for AI-generated code across multiple languages
5
5
  License: MIT
6
6
  License-File: LICENSE
@@ -24,6 +24,7 @@ Classifier: Topic :: Software Development :: Testing
24
24
  Classifier: Topic :: Utilities
25
25
  Classifier: Typing :: Typed
26
26
  Requires-Dist: click (>=8.1.0,<9.0.0)
27
+ Requires-Dist: loguru (>=0.7.3,<0.8.0)
27
28
  Requires-Dist: pyprojroot (>=0.3.0,<0.4.0)
28
29
  Requires-Dist: pyyaml (>=6.0,<7.0)
29
30
  Requires-Dist: tree-sitter (>=0.25.2,<0.26.0)
@@ -83,7 +84,7 @@ That's it. See violations, fix them, ship better code.
83
84
  | **Method Property** | Methods that should be @property | `thailint method-property src/` | [Guide](https://thai-lint.readthedocs.io/en/latest/method-property-linter/) |
84
85
  | **File Placement** | Files in wrong directories | `thailint file-placement src/` | [Guide](https://thai-lint.readthedocs.io/en/latest/file-placement-linter/) |
85
86
  | **Lazy Ignores** | Unjustified linting suppressions | `thailint lazy-ignores src/` | [Guide](https://thai-lint.readthedocs.io/en/latest/lazy-ignores-linter/) |
86
- | **Print Statements** | Debug prints left in code | `thailint print-statements src/` | [Guide](https://thai-lint.readthedocs.io/en/latest/print-statements-linter/) |
87
+ | **Improper Logging** | Print statements and conditional verbose patterns | `thailint improper-logging src/` | [Guide](https://thai-lint.readthedocs.io/en/latest/improper-logging-linter/) |
87
88
  | **Stringly Typed** | Strings that should be enums | `thailint stringly-typed src/` | [Guide](https://thai-lint.readthedocs.io/en/latest/stringly-typed-linter/) |
88
89
  | **LBYL** | Look Before You Leap anti-patterns | `thailint lbyl src/` | [Guide](https://thai-lint.readthedocs.io/en/latest/lbyl-linter/) |
89
90
 
@@ -152,10 +153,12 @@ See [How to Ignore Violations](https://thai-lint.readthedocs.io/en/latest/how-to
152
153
  - name: Run thailint
153
154
  run: |
154
155
  pip install thai-lint
155
- thailint dry src/
156
- thailint nesting src/
156
+ thailint --parallel dry src/
157
+ thailint --parallel nesting src/
157
158
  ```
158
159
 
160
+ Use `--parallel` for faster linting on large codebases (2-4x speedup on multi-core systems).
161
+
159
162
  Exit codes: `0` = success, `1` = violations found, `2` = error.
160
163
 
161
164
  ## Documentation
@@ -7,21 +7,21 @@ src/analyzers/typescript_base.py,sha256=Dtc_2jLSNoadh53MyeW2syrJMBIMVPvotcM8SeDE
7
7
  src/api.py,sha256=pJ5l3qxccKBEY-BkANwzTgLAl1ZFq7OP6hx6LSxbhDw,4664
8
8
  src/cli/__init__.py,sha256=nMxKv4D0t09LwUm4Z5w-b7vIuyn24SFzv78BKzE3AOQ,1272
9
9
  src/cli/__main__.py,sha256=xIKI57yqB1NOw9eXnGXfU8rC2UwcAJYjDxlZbt9eP0w,671
10
- src/cli/config.py,sha256=wh9lp0Z2FUEKbYawB65Y9vrlu1Yiqe8-CD_vnRDqwEg,14467
10
+ src/cli/config.py,sha256=uAcc1NMe4uvC5619wyGIrSJtOWeOWT3No8_qBvowqXk,14293
11
11
  src/cli/config_merge.py,sha256=A1eCthiLwjj0SEhOcxa6t2hYwWMapGJpL9sCmYvUFw4,8464
12
- src/cli/linters/__init__.py,sha256=Dlx2CRHT5_QeIeCJ0horPXci9zRP_KzSz_-mg0VMbOw,2343
13
- src/cli/linters/code_patterns.py,sha256=2AcFdLPPdkJ8U883ozXE_0eLWRdiL_xfzXm4kmRn-Ro,11072
14
- src/cli/linters/code_smells.py,sha256=yrKqRD3JTVVqyPL8rwK9VPbHgccXoj2IfaLK3lBF4Jc,11782
15
- src/cli/linters/documentation.py,sha256=AhkyXfH2FbcxXT3BV6GIKkGbWCjyrz8qcKhz3sx7Yww,3289
16
- src/cli/linters/performance.py,sha256=qvCW6T84zNZi1TyuxHtsEksgCtMA5TUgt_UdSq0vqHo,9878
17
- src/cli/linters/shared.py,sha256=eVXhBDr69fRGAP4lApTR_M0gDfLeWlDAqx6VCEAWXsU,10117
18
- src/cli/linters/structure.py,sha256=EGjw0zMLdGi4lTwM1zd_FzxQ7A-AG3fNPcKcF5uGFSo,10748
19
- src/cli/linters/structure_quality.py,sha256=0npTcI6OUMVmvd-Ha6NjDZb2Dn1EhpfTLXmMRB69qaI,10739
20
- src/cli/main.py,sha256=mRyRN_IQI2WyqDCxI8vuzdhbkCkVrK6INfJPjU8ayIU,3844
21
- src/cli/utils.py,sha256=wFMvW7ScseHxKgvAmuQCWWHBGbtBzu8oPwATsCyNsfU,12610
12
+ src/cli/linters/__init__.py,sha256=Gf3_Ksh4a1TB86j4O2weCwrdCujOYvJ_YSOLFeG60J4,2501
13
+ src/cli/linters/code_patterns.py,sha256=0ajlHI9kesHryBjF_YBHltMi2gjkPPacMxYoJ1h0Qck,11463
14
+ src/cli/linters/code_smells.py,sha256=9J_CPdCFDv6fHQwfLP01PgBnF1c8DwioeIICLA-N7Zo,11608
15
+ src/cli/linters/documentation.py,sha256=bvrW4OfgeKOYlzzHv3Zl-XjGjItviX_zLlP94btlUkI,3211
16
+ src/cli/linters/performance.py,sha256=TZoe8hs-5j68tUiiKRwnhdGEKm432QlmwAG4eAqgcVo,9747
17
+ src/cli/linters/shared.py,sha256=dC8SS8UxJR3WvQr1SkWJAf-AZalN-u-eDrM-FXmKqtw,10043
18
+ src/cli/linters/structure.py,sha256=4SxM2ekq2OPDQfEXxKuQAEn7pqt6cGrLgHLqZ4S57eQ,10636
19
+ src/cli/linters/structure_quality.py,sha256=5cCPz9Jmtvzr3hZZjac3aVFmUIH7HLlkshv_rvs12xI,10627
20
+ src/cli/main.py,sha256=VUtesVpLXeRP9fb2Ko_imGeUybO7JhImsJnHYxQTIN4,3852
21
+ src/cli/utils.py,sha256=5IYu9KltZEg76wgbW-qfKwcXzCK3qsEuvgnr4i_UkqY,12437
22
22
  src/cli_main.py,sha256=C0Ey7YNlG3ipqb3KsJZ8rL8PJ4ueVp_45IUirGidvHI,1618
23
23
  src/config.py,sha256=O3ixzsYekGjlggmIsawCU1bctOa0MyG2IczHpg3mGyw,12753
24
- src/core/__init__.py,sha256=5FtsDvhMt4SNRx3pbcGURrxn135XRbeRrjSUxiXwkNc,381
24
+ src/core/__init__.py,sha256=1iGdoUB15Hi7xT7jwxUybP3e5IbQ6yiM34IbFNJRCaE,703
25
25
  src/core/base.py,sha256=u5A8geprlKnsJk4ShiLHTKXRekZUB4I6rPQWxgiFeto,8019
26
26
  src/core/cli_utils.py,sha256=o7lWPSlic96lRyfcsmBf8S1ej25M1-wlclx8_Eup20g,7446
27
27
  src/core/config_parser.py,sha256=CRHV2-csxag6yQzx_4IYYz57QSUYjPkeSb0XvOyshRI,4272
@@ -29,6 +29,7 @@ src/core/constants.py,sha256=PKtPDqk6k9VuOSgjq1FAdi2CTvlnhdXvLj91dNaMDTA,1584
29
29
  src/core/linter_utils.py,sha256=StnKFzJgSvLyao1S0LpTKhsXo8nOwpdKpxo7mXl5PIg,8594
30
30
  src/core/python_lint_rule.py,sha256=OpdIDLPV1MDtmjy6GPrrOA3_rRV2_x14keHAQQtI_pc,3516
31
31
  src/core/registry.py,sha256=yRA8mQLiZwjmgxl1wSTgdj1cuo_QXuRdrXt3NpCBUgE,3285
32
+ src/core/rule_aliases.py,sha256=EEPrI2syrK18Zg1oHZBMNiU1Z-zIdORJjXTAN738h1A,2570
32
33
  src/core/rule_discovery.py,sha256=tgRH-BJGKsQTxfa249yrY7UJuonRjobMCENqmhcbAeY,5496
33
34
  src/core/types.py,sha256=SElFzf_VSrAMsoiE0aU8ZYXuvKqdfwfM5umUHx4eT8w,3342
34
35
  src/core/violation_builder.py,sha256=dOPFfZx6U5_TMgaTop-4SUV2jHOZjBo67uwgiS_s-uE,6694
@@ -40,7 +41,7 @@ src/linter_config/directive_markers.py,sha256=nRc2Mp3B1mn6tu1XH88ugMxWffk1k8OUNo
40
41
  src/linter_config/ignore.py,sha256=40afiMsu1zC29RiwGX8hUPUqM056H_wcOgZZUNOwIes,13111
41
42
  src/linter_config/loader.py,sha256=K6mKRkP2jgwar-pwBoJGWgwynLVjqdez-l3Nd6bUCMk,3363
42
43
  src/linter_config/pattern_utils.py,sha256=BjV95SySST3HqZBwF1Og8yoHqFxuqQ16VPCacE21ks0,2056
43
- src/linter_config/rule_matcher.py,sha256=EWqSv4UY90fWps3AzDCSF7PZCbYyFTEBZT2h_txcRms,2779
44
+ src/linter_config/rule_matcher.py,sha256=5q-cO9S2JbIB3Hr1BU4yAPmbS89eE7y7leiMfaMCKVk,4555
44
45
  src/linters/__init__.py,sha256=-nnNsL8E5-2p9qlLKp_TaShHAjPH-NacOEU1sXmAR9k,77
45
46
  src/linters/collection_pipeline/__init__.py,sha256=BcnbY3wgJB1XLfZ9J9qfUJQ1_yCo_THjGDTppxJEMZY,3231
46
47
  src/linters/collection_pipeline/any_all_analyzer.py,sha256=u8NGIYrJTuP3U6je5rkeEUV2Bh1yePC12vl-czAU5GU,7554
@@ -170,9 +171,11 @@ src/linters/performance/regex_analyzer.py,sha256=GZKf3jWWH28TxGlTqDeyd97JDbxjIT1
170
171
  src/linters/performance/regex_linter.py,sha256=velv84oQ3TxP9cJlYmSts3_DjS1h-RINuZDmQx-YatU,5078
171
172
  src/linters/performance/typescript_analyzer.py,sha256=t0jvLvVG97ffgC-4KhXobKa9iG9iGREOV5Kta7XsbFw,8740
172
173
  src/linters/performance/violation_builder.py,sha256=q4fy3SVVrveyYYg9O2-MZfWjlHlKRP3llH1-8VQ6sUU,5752
173
- src/linters/print_statements/__init__.py,sha256=yhvdTFSqBB4UDreeadTHKFzhayxeT6JkF3yxUHMgn1g,1893
174
+ src/linters/print_statements/__init__.py,sha256=xvqQOc3Lp_YAJChFQh-DnX9KCkj4hksRBFFE_7i5nKA,2268
175
+ src/linters/print_statements/conditional_verbose_analyzer.py,sha256=qhspk0WAC3zGBVGsLFogtbyQrVnb6doLnXhcgbZFdFc,6475
176
+ src/linters/print_statements/conditional_verbose_rule.py,sha256=uGsKXDbuHBTHfFj3ScqK1xh-BvAJ7TrJhWgpHHthLoE,9290
174
177
  src/linters/print_statements/config.py,sha256=rth3XmzqZGzXkRXDIVZswdtNOXIe1vIRaF46tVLKnyQ,3041
175
- src/linters/print_statements/linter.py,sha256=CcKolaaHYJzhpxWXthYZ7xXhfTnxmyOIhuE40Ly3ofA,14351
178
+ src/linters/print_statements/linter.py,sha256=er--FN_uu6C3wxxUti13-wG1GkjeVU0Mx_2uHGGFxcc,14376
176
179
  src/linters/print_statements/python_analyzer.py,sha256=48IDRQEv861B90qCl5w8ASxcXR7juZ429YX2ST9n2ic,5028
177
180
  src/linters/print_statements/typescript_analyzer.py,sha256=EFE3bjRENvCPEYmNNxZ4jiq1VCA-rEUAJ_VFWJApLqY,4935
178
181
  src/linters/print_statements/violation_builder.py,sha256=Vs5m3AnWjrQqQHf6JJDaPP5B1V3YNl5pepG_oiTJnx4,3333
@@ -220,8 +223,8 @@ src/orchestrator/language_detector.py,sha256=ALt2BEZKXQM2dWr1ChF9lZVj83YF4Bl9xwr
220
223
  src/templates/thailint_config_template.yaml,sha256=57ZtLxnIoOHtR5Ejq3clb4nhY9J4n6h36XFb79ZZPlc,12020
221
224
  src/utils/__init__.py,sha256=NiBtKeQ09Y3kuUzeN4O1JNfUIYPQDS2AP1l5ODq-Dec,125
222
225
  src/utils/project_root.py,sha256=aaxUM-LQ1okrPClmZWPFd_D09W3V1ArgJiidEEp_eU8,6262
223
- thailint-0.15.6.dist-info/METADATA,sha256=0ZW5TC2eyix3Bil_XocUFO4fcOCK037NB3d0CLvCdkI,7202
224
- thailint-0.15.6.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
225
- thailint-0.15.6.dist-info/entry_points.txt,sha256=DNoGUlxpaMFqxQDgHp1yeGqohOjdFR-kH19uHYi3OUY,72
226
- thailint-0.15.6.dist-info/licenses/LICENSE,sha256=kxh1J0Sb62XvhNJ6MZsVNe8PqNVJ7LHRn_EWa-T3djw,1070
227
- thailint-0.15.6.dist-info/RECORD,,
226
+ thailint-0.16.0.dist-info/METADATA,sha256=Q5acUtAfA1CtsNVOj3tdeyU8Ly05QycjyDW6QXBD7Pk,7381
227
+ thailint-0.16.0.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
228
+ thailint-0.16.0.dist-info/entry_points.txt,sha256=DNoGUlxpaMFqxQDgHp1yeGqohOjdFR-kH19uHYi3OUY,72
229
+ thailint-0.16.0.dist-info/licenses/LICENSE,sha256=kxh1J0Sb62XvhNJ6MZsVNe8PqNVJ7LHRn_EWa-T3djw,1070
230
+ thailint-0.16.0.dist-info/RECORD,,