truthound-dashboard 1.3.0__py3-none-any.whl → 1.4.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.
- truthound_dashboard/api/alerts.py +258 -0
- truthound_dashboard/api/anomaly.py +1302 -0
- truthound_dashboard/api/cross_alerts.py +352 -0
- truthound_dashboard/api/deps.py +143 -0
- truthound_dashboard/api/drift_monitor.py +540 -0
- truthound_dashboard/api/lineage.py +1151 -0
- truthound_dashboard/api/maintenance.py +363 -0
- truthound_dashboard/api/middleware.py +373 -1
- truthound_dashboard/api/model_monitoring.py +805 -0
- truthound_dashboard/api/notifications_advanced.py +2452 -0
- truthound_dashboard/api/plugins.py +2096 -0
- truthound_dashboard/api/profile.py +211 -14
- truthound_dashboard/api/reports.py +853 -0
- truthound_dashboard/api/router.py +147 -0
- truthound_dashboard/api/rule_suggestions.py +310 -0
- truthound_dashboard/api/schema_evolution.py +231 -0
- truthound_dashboard/api/sources.py +47 -3
- truthound_dashboard/api/triggers.py +190 -0
- truthound_dashboard/api/validations.py +13 -0
- truthound_dashboard/api/validators.py +333 -4
- truthound_dashboard/api/versioning.py +309 -0
- truthound_dashboard/api/websocket.py +301 -0
- truthound_dashboard/core/__init__.py +27 -0
- truthound_dashboard/core/anomaly.py +1395 -0
- truthound_dashboard/core/anomaly_explainer.py +633 -0
- truthound_dashboard/core/cache.py +206 -0
- truthound_dashboard/core/cached_services.py +422 -0
- truthound_dashboard/core/charts.py +352 -0
- truthound_dashboard/core/connections.py +1069 -42
- truthound_dashboard/core/cross_alerts.py +837 -0
- truthound_dashboard/core/drift_monitor.py +1477 -0
- truthound_dashboard/core/drift_sampling.py +669 -0
- truthound_dashboard/core/i18n/__init__.py +42 -0
- truthound_dashboard/core/i18n/detector.py +173 -0
- truthound_dashboard/core/i18n/messages.py +564 -0
- truthound_dashboard/core/lineage.py +971 -0
- truthound_dashboard/core/maintenance.py +443 -5
- truthound_dashboard/core/model_monitoring.py +1043 -0
- truthound_dashboard/core/notifications/channels.py +1020 -1
- truthound_dashboard/core/notifications/deduplication/__init__.py +143 -0
- truthound_dashboard/core/notifications/deduplication/policies.py +274 -0
- truthound_dashboard/core/notifications/deduplication/service.py +400 -0
- truthound_dashboard/core/notifications/deduplication/stores.py +2365 -0
- truthound_dashboard/core/notifications/deduplication/strategies.py +422 -0
- truthound_dashboard/core/notifications/dispatcher.py +43 -0
- truthound_dashboard/core/notifications/escalation/__init__.py +149 -0
- truthound_dashboard/core/notifications/escalation/backends.py +1384 -0
- truthound_dashboard/core/notifications/escalation/engine.py +429 -0
- truthound_dashboard/core/notifications/escalation/models.py +336 -0
- truthound_dashboard/core/notifications/escalation/scheduler.py +1187 -0
- truthound_dashboard/core/notifications/escalation/state_machine.py +330 -0
- truthound_dashboard/core/notifications/escalation/stores.py +2896 -0
- truthound_dashboard/core/notifications/events.py +49 -0
- truthound_dashboard/core/notifications/metrics/__init__.py +115 -0
- truthound_dashboard/core/notifications/metrics/base.py +528 -0
- truthound_dashboard/core/notifications/metrics/collectors.py +583 -0
- truthound_dashboard/core/notifications/routing/__init__.py +169 -0
- truthound_dashboard/core/notifications/routing/combinators.py +184 -0
- truthound_dashboard/core/notifications/routing/config.py +375 -0
- truthound_dashboard/core/notifications/routing/config_parser.py +867 -0
- truthound_dashboard/core/notifications/routing/engine.py +382 -0
- truthound_dashboard/core/notifications/routing/expression_engine.py +1269 -0
- truthound_dashboard/core/notifications/routing/jinja2_engine.py +774 -0
- truthound_dashboard/core/notifications/routing/rules.py +625 -0
- truthound_dashboard/core/notifications/routing/validator.py +678 -0
- truthound_dashboard/core/notifications/service.py +2 -0
- truthound_dashboard/core/notifications/stats_aggregator.py +850 -0
- truthound_dashboard/core/notifications/throttling/__init__.py +83 -0
- truthound_dashboard/core/notifications/throttling/builder.py +311 -0
- truthound_dashboard/core/notifications/throttling/stores.py +1859 -0
- truthound_dashboard/core/notifications/throttling/throttlers.py +633 -0
- truthound_dashboard/core/openlineage.py +1028 -0
- truthound_dashboard/core/plugins/__init__.py +39 -0
- truthound_dashboard/core/plugins/docs/__init__.py +39 -0
- truthound_dashboard/core/plugins/docs/extractor.py +703 -0
- truthound_dashboard/core/plugins/docs/renderers.py +804 -0
- truthound_dashboard/core/plugins/hooks/__init__.py +63 -0
- truthound_dashboard/core/plugins/hooks/decorators.py +367 -0
- truthound_dashboard/core/plugins/hooks/manager.py +403 -0
- truthound_dashboard/core/plugins/hooks/protocols.py +265 -0
- truthound_dashboard/core/plugins/lifecycle/__init__.py +41 -0
- truthound_dashboard/core/plugins/lifecycle/hot_reload.py +584 -0
- truthound_dashboard/core/plugins/lifecycle/machine.py +419 -0
- truthound_dashboard/core/plugins/lifecycle/states.py +266 -0
- truthound_dashboard/core/plugins/loader.py +504 -0
- truthound_dashboard/core/plugins/registry.py +810 -0
- truthound_dashboard/core/plugins/reporter_executor.py +588 -0
- truthound_dashboard/core/plugins/sandbox/__init__.py +59 -0
- truthound_dashboard/core/plugins/sandbox/code_validator.py +243 -0
- truthound_dashboard/core/plugins/sandbox/engines.py +770 -0
- truthound_dashboard/core/plugins/sandbox/protocols.py +194 -0
- truthound_dashboard/core/plugins/sandbox.py +617 -0
- truthound_dashboard/core/plugins/security/__init__.py +68 -0
- truthound_dashboard/core/plugins/security/analyzer.py +535 -0
- truthound_dashboard/core/plugins/security/policies.py +311 -0
- truthound_dashboard/core/plugins/security/protocols.py +296 -0
- truthound_dashboard/core/plugins/security/signing.py +842 -0
- truthound_dashboard/core/plugins/security.py +446 -0
- truthound_dashboard/core/plugins/validator_executor.py +401 -0
- truthound_dashboard/core/plugins/versioning/__init__.py +51 -0
- truthound_dashboard/core/plugins/versioning/constraints.py +377 -0
- truthound_dashboard/core/plugins/versioning/dependencies.py +541 -0
- truthound_dashboard/core/plugins/versioning/semver.py +266 -0
- truthound_dashboard/core/profile_comparison.py +601 -0
- truthound_dashboard/core/report_history.py +570 -0
- truthound_dashboard/core/reporters/__init__.py +57 -0
- truthound_dashboard/core/reporters/base.py +296 -0
- truthound_dashboard/core/reporters/csv_reporter.py +155 -0
- truthound_dashboard/core/reporters/html_reporter.py +598 -0
- truthound_dashboard/core/reporters/i18n/__init__.py +65 -0
- truthound_dashboard/core/reporters/i18n/base.py +494 -0
- truthound_dashboard/core/reporters/i18n/catalogs.py +930 -0
- truthound_dashboard/core/reporters/json_reporter.py +160 -0
- truthound_dashboard/core/reporters/junit_reporter.py +233 -0
- truthound_dashboard/core/reporters/markdown_reporter.py +207 -0
- truthound_dashboard/core/reporters/pdf_reporter.py +209 -0
- truthound_dashboard/core/reporters/registry.py +272 -0
- truthound_dashboard/core/rule_generator.py +2088 -0
- truthound_dashboard/core/scheduler.py +822 -12
- truthound_dashboard/core/schema_evolution.py +858 -0
- truthound_dashboard/core/services.py +152 -9
- truthound_dashboard/core/statistics.py +718 -0
- truthound_dashboard/core/streaming_anomaly.py +883 -0
- truthound_dashboard/core/triggers/__init__.py +45 -0
- truthound_dashboard/core/triggers/base.py +226 -0
- truthound_dashboard/core/triggers/evaluators.py +609 -0
- truthound_dashboard/core/triggers/factory.py +363 -0
- truthound_dashboard/core/unified_alerts.py +870 -0
- truthound_dashboard/core/validation_limits.py +509 -0
- truthound_dashboard/core/versioning.py +709 -0
- truthound_dashboard/core/websocket/__init__.py +59 -0
- truthound_dashboard/core/websocket/manager.py +512 -0
- truthound_dashboard/core/websocket/messages.py +130 -0
- truthound_dashboard/db/__init__.py +30 -0
- truthound_dashboard/db/models.py +3375 -3
- truthound_dashboard/main.py +22 -0
- truthound_dashboard/schemas/__init__.py +396 -1
- truthound_dashboard/schemas/anomaly.py +1258 -0
- truthound_dashboard/schemas/base.py +4 -0
- truthound_dashboard/schemas/cross_alerts.py +334 -0
- truthound_dashboard/schemas/drift_monitor.py +890 -0
- truthound_dashboard/schemas/lineage.py +428 -0
- truthound_dashboard/schemas/maintenance.py +154 -0
- truthound_dashboard/schemas/model_monitoring.py +374 -0
- truthound_dashboard/schemas/notifications_advanced.py +1363 -0
- truthound_dashboard/schemas/openlineage.py +704 -0
- truthound_dashboard/schemas/plugins.py +1293 -0
- truthound_dashboard/schemas/profile.py +420 -34
- truthound_dashboard/schemas/profile_comparison.py +242 -0
- truthound_dashboard/schemas/reports.py +285 -0
- truthound_dashboard/schemas/rule_suggestion.py +434 -0
- truthound_dashboard/schemas/schema_evolution.py +164 -0
- truthound_dashboard/schemas/source.py +117 -2
- truthound_dashboard/schemas/triggers.py +511 -0
- truthound_dashboard/schemas/unified_alerts.py +223 -0
- truthound_dashboard/schemas/validation.py +25 -1
- truthound_dashboard/schemas/validators/__init__.py +11 -0
- truthound_dashboard/schemas/validators/base.py +151 -0
- truthound_dashboard/schemas/versioning.py +152 -0
- truthound_dashboard/static/index.html +2 -2
- {truthound_dashboard-1.3.0.dist-info → truthound_dashboard-1.4.0.dist-info}/METADATA +142 -18
- truthound_dashboard-1.4.0.dist-info/RECORD +239 -0
- truthound_dashboard/static/assets/index-BCA8H1hO.js +0 -574
- truthound_dashboard/static/assets/index-BNsSQ2fN.css +0 -1
- truthound_dashboard/static/assets/unmerged_dictionaries-CsJWCRx9.js +0 -1
- truthound_dashboard-1.3.0.dist-info/RECORD +0 -110
- {truthound_dashboard-1.3.0.dist-info → truthound_dashboard-1.4.0.dist-info}/WHEEL +0 -0
- {truthound_dashboard-1.3.0.dist-info → truthound_dashboard-1.4.0.dist-info}/entry_points.txt +0 -0
- {truthound_dashboard-1.3.0.dist-info → truthound_dashboard-1.4.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
"""Semantic Versioning Implementation.
|
|
2
|
+
|
|
3
|
+
This module provides semantic versioning (semver) parsing
|
|
4
|
+
and comparison following the Semantic Versioning 2.0.0 spec.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import re
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
from functools import total_ordering
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# Semver regex pattern
|
|
16
|
+
SEMVER_PATTERN = re.compile(
|
|
17
|
+
r"^v?" # Optional 'v' prefix
|
|
18
|
+
r"(?P<major>0|[1-9]\d*)"
|
|
19
|
+
r"\.(?P<minor>0|[1-9]\d*)"
|
|
20
|
+
r"\.(?P<patch>0|[1-9]\d*)"
|
|
21
|
+
r"(?:-(?P<prerelease>(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)"
|
|
22
|
+
r"(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?"
|
|
23
|
+
r"(?:\+(?P<build>[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$"
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@total_ordering
|
|
28
|
+
@dataclass
|
|
29
|
+
class Version:
|
|
30
|
+
"""Semantic version representation.
|
|
31
|
+
|
|
32
|
+
Attributes:
|
|
33
|
+
major: Major version (breaking changes).
|
|
34
|
+
minor: Minor version (new features).
|
|
35
|
+
patch: Patch version (bug fixes).
|
|
36
|
+
prerelease: Pre-release identifier (e.g., alpha.1).
|
|
37
|
+
build: Build metadata (e.g., build.123).
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
major: int
|
|
41
|
+
minor: int
|
|
42
|
+
patch: int
|
|
43
|
+
prerelease: str | None = None
|
|
44
|
+
build: str | None = None
|
|
45
|
+
|
|
46
|
+
def __str__(self) -> str:
|
|
47
|
+
"""Return string representation."""
|
|
48
|
+
version = f"{self.major}.{self.minor}.{self.patch}"
|
|
49
|
+
if self.prerelease:
|
|
50
|
+
version += f"-{self.prerelease}"
|
|
51
|
+
if self.build:
|
|
52
|
+
version += f"+{self.build}"
|
|
53
|
+
return version
|
|
54
|
+
|
|
55
|
+
def __repr__(self) -> str:
|
|
56
|
+
"""Return repr string."""
|
|
57
|
+
return f"Version('{self}')"
|
|
58
|
+
|
|
59
|
+
def __hash__(self) -> int:
|
|
60
|
+
"""Hash based on version components (excluding build)."""
|
|
61
|
+
return hash((self.major, self.minor, self.patch, self.prerelease))
|
|
62
|
+
|
|
63
|
+
def __eq__(self, other: object) -> bool:
|
|
64
|
+
"""Check equality (build metadata is ignored)."""
|
|
65
|
+
if not isinstance(other, Version):
|
|
66
|
+
return NotImplemented
|
|
67
|
+
return (
|
|
68
|
+
self.major == other.major
|
|
69
|
+
and self.minor == other.minor
|
|
70
|
+
and self.patch == other.patch
|
|
71
|
+
and self.prerelease == other.prerelease
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
def __lt__(self, other: object) -> bool:
|
|
75
|
+
"""Compare versions."""
|
|
76
|
+
if not isinstance(other, Version):
|
|
77
|
+
return NotImplemented
|
|
78
|
+
|
|
79
|
+
# Compare major.minor.patch
|
|
80
|
+
if (self.major, self.minor, self.patch) != (
|
|
81
|
+
other.major,
|
|
82
|
+
other.minor,
|
|
83
|
+
other.patch,
|
|
84
|
+
):
|
|
85
|
+
return (self.major, self.minor, self.patch) < (
|
|
86
|
+
other.major,
|
|
87
|
+
other.minor,
|
|
88
|
+
other.patch,
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
# Pre-release versions have lower precedence than release
|
|
92
|
+
if self.prerelease and not other.prerelease:
|
|
93
|
+
return True
|
|
94
|
+
if not self.prerelease and other.prerelease:
|
|
95
|
+
return False
|
|
96
|
+
|
|
97
|
+
# Compare pre-release identifiers
|
|
98
|
+
if self.prerelease and other.prerelease:
|
|
99
|
+
return self._compare_prerelease(self.prerelease, other.prerelease) < 0
|
|
100
|
+
|
|
101
|
+
return False
|
|
102
|
+
|
|
103
|
+
@staticmethod
|
|
104
|
+
def _compare_prerelease(a: str, b: str) -> int:
|
|
105
|
+
"""Compare pre-release strings.
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
-1 if a < b, 0 if a == b, 1 if a > b.
|
|
109
|
+
"""
|
|
110
|
+
a_parts = a.split(".")
|
|
111
|
+
b_parts = b.split(".")
|
|
112
|
+
|
|
113
|
+
for i in range(max(len(a_parts), len(b_parts))):
|
|
114
|
+
if i >= len(a_parts):
|
|
115
|
+
return -1
|
|
116
|
+
if i >= len(b_parts):
|
|
117
|
+
return 1
|
|
118
|
+
|
|
119
|
+
a_part = a_parts[i]
|
|
120
|
+
b_part = b_parts[i]
|
|
121
|
+
|
|
122
|
+
# Numeric identifiers have lower precedence
|
|
123
|
+
a_is_num = a_part.isdigit()
|
|
124
|
+
b_is_num = b_part.isdigit()
|
|
125
|
+
|
|
126
|
+
if a_is_num and b_is_num:
|
|
127
|
+
a_num = int(a_part)
|
|
128
|
+
b_num = int(b_part)
|
|
129
|
+
if a_num != b_num:
|
|
130
|
+
return -1 if a_num < b_num else 1
|
|
131
|
+
elif a_is_num:
|
|
132
|
+
return -1
|
|
133
|
+
elif b_is_num:
|
|
134
|
+
return 1
|
|
135
|
+
else:
|
|
136
|
+
if a_part != b_part:
|
|
137
|
+
return -1 if a_part < b_part else 1
|
|
138
|
+
|
|
139
|
+
return 0
|
|
140
|
+
|
|
141
|
+
def bump_major(self) -> "Version":
|
|
142
|
+
"""Return a new version with major bumped."""
|
|
143
|
+
return Version(self.major + 1, 0, 0)
|
|
144
|
+
|
|
145
|
+
def bump_minor(self) -> "Version":
|
|
146
|
+
"""Return a new version with minor bumped."""
|
|
147
|
+
return Version(self.major, self.minor + 1, 0)
|
|
148
|
+
|
|
149
|
+
def bump_patch(self) -> "Version":
|
|
150
|
+
"""Return a new version with patch bumped."""
|
|
151
|
+
return Version(self.major, self.minor, self.patch + 1)
|
|
152
|
+
|
|
153
|
+
def to_dict(self) -> dict[str, Any]:
|
|
154
|
+
"""Convert to dictionary."""
|
|
155
|
+
return {
|
|
156
|
+
"major": self.major,
|
|
157
|
+
"minor": self.minor,
|
|
158
|
+
"patch": self.patch,
|
|
159
|
+
"prerelease": self.prerelease,
|
|
160
|
+
"build": self.build,
|
|
161
|
+
"string": str(self),
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
@classmethod
|
|
165
|
+
def from_dict(cls, data: dict[str, Any]) -> "Version":
|
|
166
|
+
"""Create from dictionary."""
|
|
167
|
+
return cls(
|
|
168
|
+
major=data["major"],
|
|
169
|
+
minor=data["minor"],
|
|
170
|
+
patch=data["patch"],
|
|
171
|
+
prerelease=data.get("prerelease"),
|
|
172
|
+
build=data.get("build"),
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
def is_prerelease(self) -> bool:
|
|
176
|
+
"""Check if this is a pre-release version."""
|
|
177
|
+
return self.prerelease is not None
|
|
178
|
+
|
|
179
|
+
def is_stable(self) -> bool:
|
|
180
|
+
"""Check if this is a stable release (major > 0, no prerelease)."""
|
|
181
|
+
return self.major > 0 and not self.prerelease
|
|
182
|
+
|
|
183
|
+
def is_compatible_with(self, other: "Version") -> bool:
|
|
184
|
+
"""Check if this version is API-compatible with another.
|
|
185
|
+
|
|
186
|
+
For semver, versions are compatible if they have the same major
|
|
187
|
+
version (for major > 0) or same major.minor (for major == 0).
|
|
188
|
+
"""
|
|
189
|
+
if self.major == 0 and other.major == 0:
|
|
190
|
+
# 0.x.y versions: minor change is breaking
|
|
191
|
+
return self.minor == other.minor
|
|
192
|
+
return self.major == other.major
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def parse_version(version_str: str) -> Version:
|
|
196
|
+
"""Parse a version string into a Version object.
|
|
197
|
+
|
|
198
|
+
Args:
|
|
199
|
+
version_str: Version string (e.g., "1.2.3", "v1.2.3-alpha.1+build.123").
|
|
200
|
+
|
|
201
|
+
Returns:
|
|
202
|
+
Version object.
|
|
203
|
+
|
|
204
|
+
Raises:
|
|
205
|
+
ValueError: If version string is invalid.
|
|
206
|
+
"""
|
|
207
|
+
if not version_str:
|
|
208
|
+
raise ValueError("Empty version string")
|
|
209
|
+
|
|
210
|
+
match = SEMVER_PATTERN.match(version_str.strip())
|
|
211
|
+
if not match:
|
|
212
|
+
# Try simple parsing for common formats
|
|
213
|
+
parts = version_str.lstrip("v").split(".")
|
|
214
|
+
if len(parts) >= 3:
|
|
215
|
+
try:
|
|
216
|
+
major = int(parts[0])
|
|
217
|
+
minor = int(parts[1])
|
|
218
|
+
patch_str = parts[2].split("-")[0].split("+")[0]
|
|
219
|
+
patch = int(patch_str)
|
|
220
|
+
prerelease = None
|
|
221
|
+
build = None
|
|
222
|
+
|
|
223
|
+
if "-" in version_str:
|
|
224
|
+
pre_build = version_str.split("-", 1)[1]
|
|
225
|
+
if "+" in pre_build:
|
|
226
|
+
prerelease, build = pre_build.split("+", 1)
|
|
227
|
+
else:
|
|
228
|
+
prerelease = pre_build
|
|
229
|
+
elif "+" in version_str:
|
|
230
|
+
build = version_str.split("+", 1)[1]
|
|
231
|
+
|
|
232
|
+
return Version(major, minor, patch, prerelease, build)
|
|
233
|
+
except (ValueError, IndexError):
|
|
234
|
+
pass
|
|
235
|
+
|
|
236
|
+
raise ValueError(f"Invalid version string: {version_str}")
|
|
237
|
+
|
|
238
|
+
return Version(
|
|
239
|
+
major=int(match.group("major")),
|
|
240
|
+
minor=int(match.group("minor")),
|
|
241
|
+
patch=int(match.group("patch")),
|
|
242
|
+
prerelease=match.group("prerelease"),
|
|
243
|
+
build=match.group("build"),
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def compare_versions(v1: str | Version, v2: str | Version) -> int:
|
|
248
|
+
"""Compare two versions.
|
|
249
|
+
|
|
250
|
+
Args:
|
|
251
|
+
v1: First version.
|
|
252
|
+
v2: Second version.
|
|
253
|
+
|
|
254
|
+
Returns:
|
|
255
|
+
-1 if v1 < v2, 0 if v1 == v2, 1 if v1 > v2.
|
|
256
|
+
"""
|
|
257
|
+
if isinstance(v1, str):
|
|
258
|
+
v1 = parse_version(v1)
|
|
259
|
+
if isinstance(v2, str):
|
|
260
|
+
v2 = parse_version(v2)
|
|
261
|
+
|
|
262
|
+
if v1 < v2:
|
|
263
|
+
return -1
|
|
264
|
+
elif v1 > v2:
|
|
265
|
+
return 1
|
|
266
|
+
return 0
|