tellaro-query-language 0.2.2__py3-none-any.whl → 0.2.5__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.
@@ -1,30 +1,32 @@
1
- tql/__init__.py,sha256=mRrrtun-1Xx9k8g0aaiqxYhhNAwWEyRC4zrVMO49Kkg,1260
1
+ tql/__init__.py,sha256=eqti5Fmu8EjD-NbCwqb1UKGJQ0OpWJLREsVMqqn6Hs4,1260
2
2
  tql/analyzer.py,sha256=Sfzj6f7YzqylT8HIL9hDbXdhl0lf8q8DNoafrxkD-F8,15456
3
3
  tql/cache/__init__.py,sha256=GIzIEMZUZEYJj72sAhuVLEG-OJEKUG2srUWNM3Ix-T8,213
4
- tql/cache/base.py,sha256=0b-8uyh3JltayGmXQI45snTqsM5sQu9u0KcNvZIRa-I,687
5
- tql/cache/memory.py,sha256=ibcmQSAxNvqCy6DksbU7gLu6UArYp1u3fW-oLubxtV0,2056
4
+ tql/cache/base.py,sha256=CwLpobv4WR7WSz99JVWAHNn-XQTtqr38Yg5UiWNXPiA,3192
5
+ tql/cache/memory.py,sha256=4nKFvq8SQSzht4JjVgml4MiEFk5pJmTCH6tHO8vwnrI,5978
6
6
  tql/cache/redis.py,sha256=ZU_IsVDvpSYpNvPfnZ4iulJDODpEGx3c4dkXLzPzPVc,2309
7
- tql/core.py,sha256=bMPrcuutY-1yvC-4M7w2y1JxNitMyBSpxPfg8ohjO60,48406
7
+ tql/cli.py,sha256=dxvYvq0TmLWS5vHAkhdgTUlJK42vORjH8ttNkjIcgqM,15257
8
+ tql/core.py,sha256=_OlFGTrvJ8cBz3OliRxSz-IbD6e1t_nYC5YZFVkOAfc,58413
8
9
  tql/core_components/README.md,sha256=Rm7w4UHdQ0vPBEFybE5b62IOvSA5Nzq2GRvtBHOapmc,3068
9
10
  tql/core_components/__init__.py,sha256=v8BBybPlqV7dkVY9mw1mblvqyAFJZ7Pf_bEc-jAL7FI,643
10
11
  tql/core_components/file_operations.py,sha256=Jr0kkxz_OP2KHOAsIr7KMtYe_lbu8LuBUySt2LQbjJw,3925
11
- tql/core_components/opensearch_operations.py,sha256=KvmK1FnkGZFBjBysH_sDjzIRnyUcNn7wzLzuRr1rBlg,54264
12
+ tql/core_components/opensearch_operations.py,sha256=zgxGiDpXyPW0ZUX-StpZXxf84s8eLxSymAGM5UUJimk,55253
12
13
  tql/core_components/stats_operations.py,sha256=aqTGAqIFvR6EkSbJEd0qft8Ldy8uiTrK2XI9o5bZUOs,8014
13
14
  tql/core_components/validation_operations.py,sha256=_VPXh0HABBjsXF99jFT7B6-5QAPsADOCy6poinGrxeE,22454
14
- tql/evaluator.py,sha256=_JYr-wK3F1wvBoNGIBiAEaP6Ot1g2qxZ4lOjPdOqvDk,17698
15
+ tql/evaluator.py,sha256=6BtC0njH_aR_lXiU6GU5vM5MRhQeSoxL7F95xh-2-ho,17903
15
16
  tql/evaluator_components/README.md,sha256=c59yf2au34yPhrru7JWgGop_ORteB6w5vfMhsac8j3k,3882
16
17
  tql/evaluator_components/__init__.py,sha256=DourRUSYXWPnCghBFj7W0YfMeymT3X8YTDCwnLIyP1c,535
17
18
  tql/evaluator_components/field_access.py,sha256=BuXvL9jlv4H77neT70Vh7_qokmzs-d4EbSDA2FB1IT0,6435
18
- tql/evaluator_components/special_expressions.py,sha256=K6M5pW4Re2kEqxfxj9sc7I_M1tU3pn6LKJ2AfjHeciA,12917
19
- tql/evaluator_components/value_comparison.py,sha256=pL7-hxdNbzJ53DrTSiDdd7KYbVLChuNwFRLjG7P_1KM,17939
20
- tql/exceptions.py,sha256=hatIixXci6p57J9RrkfdvmKM_2i-JKb8ViL2kU4z7a8,5550
19
+ tql/evaluator_components/special_expressions.py,sha256=prhXnVRnFfmecgmuTz0fsefTA1clb2eyyYf-zPtzXGs,15703
20
+ tql/evaluator_components/value_comparison.py,sha256=a0Vo3BWyPxMtEgAKtUuaN0HN9Arc0A7BUVrxgZyH6_o,21200
21
+ tql/exceptions.py,sha256=GVssdBp9134Wyk1bWbcLQFU9U8yQKl5zzquTTrM22A0,5620
22
+ tql/field_type_inference.py,sha256=KOazjp8CK6s9vwOcFkQrdOwcazOwHqSIhYgX8hWCiDo,9700
21
23
  tql/geoip_normalizer.py,sha256=tvie-5xevJEeLp2KmjoXDjYdND8AvyVE7lCO8qgUzGY,10486
22
- tql/mutator_analyzer.py,sha256=OzI7t3C4H0IJOonpywE5LWz2cm5Dco5xnp2RTQOiSWg,55638
24
+ tql/mutator_analyzer.py,sha256=OWx3k5lK5aFHWU9Ez6DaIhenEZDxj9CbB0vM71xqUTw,55670
23
25
  tql/mutators/__init__.py,sha256=eTK8sRw4KXXnTZTn5ETIqwcaIek5rSUIVyZsxTwNNHA,6966
24
26
  tql/mutators/base.py,sha256=4Ze_x1sTO11OILXfcF2XN7ttyHcZ4gwn96UXFMMaC6M,2523
25
27
  tql/mutators/dns.py,sha256=1IKgHolFLRMR4TOgK0AiLjz5vDtFiqO328mVF4Vzk3s,14428
26
28
  tql/mutators/encoding.py,sha256=yt12BJrHAIJfBesP8VOSfVlvJqB1yOmEeT_8QDPvNN8,7985
27
- tql/mutators/geo.py,sha256=fFQSg_Li3KjFKS3TI26yDzrDpWsmC3MfmgcsxYoQMgM,14507
29
+ tql/mutators/geo.py,sha256=H-_5oDvuYaAG8Re17RkGjzCc6Z07YHd7Cr95g6JbnyE,16188
28
30
  tql/mutators/list.py,sha256=949ZrKKhL4INkH2Od8bq7Ey80kFX_23PEfRKueG82cU,7084
29
31
  tql/mutators/network.py,sha256=1lZpmKt1GoTfNxiXUmSXkTwJIzPQZnQEgU7ojpBSm3A,5458
30
32
  tql/mutators/security.py,sha256=XyWuPxgpCi-igHKmkbP0_0V-evOc3FG2a1igY8rQRX4,9256
@@ -34,23 +36,24 @@ tql/opensearch_components/README.md,sha256=gt-qLmmach8Kh7-QwLZmoAxxIL79XIG1EDqJu
34
36
  tql/opensearch_components/__init__.py,sha256=_zIZY8Fns7mkEcY6w2p9FNRBXtEmmPFFJEcFRfrVyXA,514
35
37
  tql/opensearch_components/field_mapping.py,sha256=fj388cKVyDXLJKi8giSiGHL9zg4cFRzy0VJ6nIsppSo,18102
36
38
  tql/opensearch_components/lucene_converter.py,sha256=OvYTZHNBktPGow1fsVm4TMlvxHSmWrnqo42lFZNxXTo,13175
37
- tql/opensearch_components/query_converter.py,sha256=vLoBqv7W3ntqUH6hcuT4PDJkGkAGSQCxMvAWC482c0g,41971
39
+ tql/opensearch_components/query_converter.py,sha256=INjX6hd-1dlCdCn_dGSBnub2mpNyUBpHXhHA5WoBQX4,41985
38
40
  tql/opensearch_mappings.py,sha256=sVLlQlE3eGD7iNNZ_m4F4j5GVzQAJhZyCqDKYRhLRh8,11531
39
- tql/opensearch_stats.py,sha256=aMV__jtlfogGBnFucsNPazORro2mYTz_C_w9uxOqsMI,24384
40
- tql/parser.py,sha256=9kewX4IbBL3W5hbq9Xhi4BGrQ4QaoWqz9AJV0Yuf9YA,78665
41
+ tql/opensearch_stats.py,sha256=sxJ4KziV-Yv1kvjo22souxjBQH3chmlm4lAgEhRBtyA,24530
42
+ tql/parser.py,sha256=y4LKYuNqE74Xx5UpHZBZE6PlVaRBiwr5i49NeMJW-jU,80383
41
43
  tql/parser_components/README.md,sha256=lvQX72ckq2zyotGs8QIHHCIFqaA7bOHwkP44wU8Zoiw,2322
42
44
  tql/parser_components/__init__.py,sha256=zBwHBMPJyHSBbaOojf6qTrJYjJg5A6tPUE8nHFdRiQs,521
43
45
  tql/parser_components/ast_builder.py,sha256=erHoeKAMzobswoRIXB9xcsZbzQ5-2ZwaYfQgRWoUAa8,9653
44
46
  tql/parser_components/error_analyzer.py,sha256=qlCD9vKyW73aeKQYI33P1OjIWSJ3LPd08wuN9cis2fU,4012
45
47
  tql/parser_components/field_extractor.py,sha256=eUEkmiYWX2OexanFqhHeX8hcIkRlfIcgMB667e0HRYs,4629
46
48
  tql/parser_components/grammar.py,sha256=h58RBshZHXgbP1EmNwmf7dny-fgVloNg-qN4Rivross,20599
47
- tql/post_processor.py,sha256=MZOJzuWTL2qdvu-AUNMryYF2D-piv8rYH5vCcrLt5-A,50069
48
- tql/scripts.py,sha256=VOr5vCjIvKlW36kwvJx7JGFIRM16IJZlbJcWlBexBtk,3728
49
- tql/stats_evaluator.py,sha256=OQZuNLwLHAtWrwAh3utdtr1fQt3tftCs6L-1G1NQCGQ,22318
49
+ tql/post_processor.py,sha256=5w_rP0V-t3AC7iAFLnlDxUtawcDFovbhbRsoZxt9yP4,51787
50
+ tql/scripts.py,sha256=DUY0H5IoGs_CNdG_oxITvLiCNVsogb2RJqUs2xXOs24,4319
51
+ tql/stats_evaluator.py,sha256=2qnjeH5Qx14qpHDS_YJn9jRPeoPUfkeiYJabBagdfRs,36126
50
52
  tql/stats_transformer.py,sha256=MT-4rDWZSySgn4Fuq9H0c-mvwFYLM6FqWpPv2rHX-rE,7588
53
+ tql/streaming_file_processor.py,sha256=cftWhYcvUo984P3ALf2CO3FoCQPJPe_2s2HLcXTp5UQ,12437
51
54
  tql/validators.py,sha256=e9MlX-zQ_O3M8YP8vXyMjKU8iiJMTh6mMK0iv0_4gTY,3771
52
- tellaro_query_language-0.2.2.dist-info/LICENSE,sha256=zRhQ85LnW55fWgAjQctckwQ67DX5Jmt64lq343ThZFU,1063
53
- tellaro_query_language-0.2.2.dist-info/METADATA,sha256=QsCXKY_0aHeMorc4PepJ84ViZbTK53suxILmr868Lkk,15740
54
- tellaro_query_language-0.2.2.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
55
- tellaro_query_language-0.2.2.dist-info/entry_points.txt,sha256=H43APfGBMsZkKsUCnFTaqprQPW-Kce2yz2qsBL3dZrw,164
56
- tellaro_query_language-0.2.2.dist-info/RECORD,,
55
+ tellaro_query_language-0.2.5.dist-info/LICENSE,sha256=eWf8lkuXlVX_8WiDpUgQvzxc1cxCeVne_e6P-pVJpwM,3038
56
+ tellaro_query_language-0.2.5.dist-info/METADATA,sha256=HCTN5lDkKfutCPvcLYTQztHwW1G4qbo2LP8cYFRayZM,21857
57
+ tellaro_query_language-0.2.5.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
58
+ tellaro_query_language-0.2.5.dist-info/entry_points.txt,sha256=D0lbIGUYuDyfcYeqju1rWcMBFzft4sZtfIlw5uPNx5g,181
59
+ tellaro_query_language-0.2.5.dist-info/RECORD,,
@@ -4,4 +4,5 @@ cov=tql.scripts:run_coverage
4
4
  lint=tql.scripts:run_lint
5
5
  lint-all=tql.scripts:run_lint_all
6
6
  tests=tql.scripts:run_tests
7
+ tql=tql.cli:main
7
8
 
tql/__init__.py CHANGED
@@ -28,7 +28,7 @@ from .opensearch_mappings import (
28
28
  get_sample_data_from_index,
29
29
  )
30
30
 
31
- __version__ = "0.1.0"
31
+ __version__ = "0.2.2"
32
32
  __all__ = [
33
33
  "TQL",
34
34
  "TQLParseError",
tql/cache/base.py CHANGED
@@ -1,25 +1,97 @@
1
- """Base cache infrastructure."""
1
+ """Base cache infrastructure for TQL.
2
+
3
+ This module provides the base CacheManager class that defines the caching
4
+ interface used throughout TQL. Concrete implementations include LocalCacheManager
5
+ for in-memory caching and RedisCacheManager for distributed caching.
6
+ """
2
7
 
3
8
  from typing import Any, Dict, Optional
4
9
 
5
10
 
6
11
  class CacheManager:
7
- """Base class for cache management."""
12
+ """Base class for cache management.
13
+
14
+ This class defines the interface for all cache implementations in TQL.
15
+ Subclasses should override these methods to provide actual caching functionality.
16
+
17
+ The base implementation provides no-op defaults that can be safely used when
18
+ caching is disabled or not needed.
19
+
20
+ Example:
21
+ >>> cache = LocalCacheManager()
22
+ >>> cache.set("user:123", {"name": "Alice", "age": 30}, ttl=3600)
23
+ >>> user = cache.get("user:123")
24
+ >>> cache.delete("user:123")
25
+ """
8
26
 
9
27
  def get(self, key: str) -> Optional[Any]:
10
- """Retrieve value from cache."""
28
+ """Retrieve a value from the cache.
29
+
30
+ Args:
31
+ key: The cache key to look up. Should be a string identifier.
32
+
33
+ Returns:
34
+ The cached value if it exists and hasn't expired, None otherwise.
35
+
36
+ Example:
37
+ >>> value = cache.get("my_key")
38
+ >>> if value is not None:
39
+ ... print(f"Found: {value}")
40
+ """
11
41
  return None
12
42
 
13
43
  def set(self, key: str, value: Any, ttl: Optional[int] = None) -> None:
14
- """Store value in cache."""
44
+ """Store a value in the cache.
45
+
46
+ Args:
47
+ key: The cache key under which to store the value.
48
+ value: The value to cache. Can be any Python object.
49
+ ttl: Time-to-live in seconds. If None or 0, the value never expires.
50
+
51
+ Example:
52
+ >>> cache.set("config", {"debug": True}, ttl=300) # Cache for 5 minutes
53
+ >>> cache.set("permanent", {"version": "1.0"}) # Never expires
54
+ """
15
55
 
16
56
  def delete(self, key: str) -> None:
17
- """Remove value from cache."""
57
+ """Remove a value from the cache.
58
+
59
+ Args:
60
+ key: The cache key to delete.
61
+
62
+ Example:
63
+ >>> cache.delete("expired_key")
64
+ """
18
65
 
19
66
  def clear_pattern(self, pattern: str) -> int: # pylint: disable=unused-argument
20
- """Clear all keys matching pattern."""
67
+ """Clear all keys matching a pattern.
68
+
69
+ Args:
70
+ pattern: A pattern string to match keys. Format depends on implementation.
71
+ For Redis: supports wildcards like "user:*" or "session:?123"
72
+ For Local: basic string matching
73
+
74
+ Returns:
75
+ The number of keys that were deleted.
76
+
77
+ Example:
78
+ >>> count = cache.clear_pattern("temp:*")
79
+ >>> print(f"Cleared {count} temporary keys")
80
+ """
21
81
  return 0
22
82
 
23
83
  def get_stats(self) -> Dict[str, Any]:
24
- """Get cache statistics."""
84
+ """Get cache statistics and metrics.
85
+
86
+ Returns:
87
+ Dictionary containing cache statistics such as:
88
+ - hit_rate: Cache hit rate percentage
89
+ - miss_rate: Cache miss rate percentage
90
+ - size: Number of items in cache
91
+ - memory_usage: Memory used by cache (if available)
92
+
93
+ Example:
94
+ >>> stats = cache.get_stats()
95
+ >>> print(f"Hit rate: {stats.get('hit_rate', 0)}%")
96
+ """
25
97
  return {}
tql/cache/memory.py CHANGED
@@ -1,63 +1,171 @@
1
- """In-memory cache implementation."""
1
+ """In-memory cache implementation for TQL.
2
+
3
+ This module provides a simple in-memory cache with TTL (time-to-live) support
4
+ and basic LRU (Least Recently Used) eviction when the cache reaches its size limit.
5
+ """
2
6
 
3
7
  import time
4
- from typing import Any, Dict, Optional, Tuple
8
+ from typing import Any, Dict, Optional
5
9
 
6
10
  from .base import CacheManager
7
11
 
8
12
 
9
13
  class LocalCacheManager(CacheManager):
10
- """Local in-memory cache using LRU."""
14
+ """Local in-memory cache with TTL and LRU eviction.
15
+
16
+ This implementation provides thread-safe in-memory caching suitable for
17
+ single-process applications. For distributed caching across multiple
18
+ processes or servers, use RedisCacheManager instead.
19
+
20
+ Features:
21
+ - TTL-based expiration
22
+ - LRU eviction when cache is full
23
+ - Hit/miss statistics tracking
24
+ - Pattern-based key clearing
25
+
26
+ Args:
27
+ max_size: Maximum number of items to store (default: 10000)
28
+ default_ttl: Default time-to-live in seconds (default: 3600 = 1 hour)
29
+
30
+ Example:
31
+ >>> cache = LocalCacheManager(max_size=1000, default_ttl=600)
32
+ >>> cache.set("user:123", {"name": "Alice"}, ttl=300)
33
+ >>> user = cache.get("user:123")
34
+ >>> stats = cache.get_stats()
35
+ >>> print(f"Hit rate: {stats['hit_rate']:.2%}")
36
+
37
+ Attributes:
38
+ max_size: Maximum cache size
39
+ default_ttl: Default TTL for cached items
40
+ """
11
41
 
12
42
  def __init__(self, max_size: int = 10000, default_ttl: int = 3600):
43
+ """Initialize the local cache.
44
+
45
+ Args:
46
+ max_size: Maximum number of items to cache before eviction starts.
47
+ default_ttl: Default expiration time in seconds for cached items.
48
+ """
13
49
  self.max_size = max_size
14
50
  self.default_ttl = default_ttl
15
- self._cache: Dict[str, Tuple[Any, float]] = {}
51
+ self._cache: Dict[str, Any] = {}
52
+ self._expiry: Dict[str, float] = {}
16
53
  self._hits = 0
17
54
  self._misses = 0
18
55
 
19
56
  def get(self, key: str) -> Optional[Any]:
20
- """Retrieve value from cache if not expired."""
57
+ """Retrieve value from cache if not expired.
58
+
59
+ Args:
60
+ key: The cache key to retrieve.
61
+
62
+ Returns:
63
+ The cached value if present and not expired, None otherwise.
64
+
65
+ Note:
66
+ This method automatically removes expired keys when accessed.
67
+ Hit/miss statistics are updated on each call.
68
+ """
21
69
  if key in self._cache:
22
- value, expiry = self._cache[key]
23
- if expiry > time.time():
70
+ expiry = self._expiry.get(key, float("inf"))
71
+ if expiry == 0 or expiry > time.time():
24
72
  self._hits += 1
25
- return value
73
+ return self._cache[key]
26
74
  else:
27
- # Expired
75
+ # Expired - clean up
28
76
  del self._cache[key]
77
+ del self._expiry[key]
29
78
  self._misses += 1
30
79
  return None
31
80
 
32
81
  def set(self, key: str, value: Any, ttl: Optional[int] = None) -> None:
33
- """Store value in cache with TTL."""
34
- if len(self._cache) >= self.max_size:
35
- # Simple eviction: remove oldest
82
+ """Store value in cache with optional TTL.
83
+
84
+ Args:
85
+ key: The cache key under which to store the value.
86
+ value: The value to cache (any Python object).
87
+ ttl: Time-to-live in seconds. If None, uses default_ttl.
88
+ If 0, the item never expires.
89
+
90
+ Note:
91
+ When the cache is full (reaches max_size), the oldest item
92
+ is evicted to make room for the new one (LRU eviction).
93
+ """
94
+ if len(self._cache) >= self.max_size and key not in self._cache:
95
+ # Simple eviction: remove oldest (first in dict)
36
96
  oldest_key = next(iter(self._cache))
37
97
  del self._cache[oldest_key]
98
+ self._expiry.pop(oldest_key, None)
38
99
 
39
- expiry = time.time() + (ttl or self.default_ttl)
40
- self._cache[key] = (value, expiry)
100
+ self._cache[key] = value
101
+ if ttl == 0:
102
+ # Never expires
103
+ self._expiry[key] = 0
104
+ else:
105
+ expiry_time = time.time() + (ttl if ttl is not None else self.default_ttl)
106
+ self._expiry[key] = expiry_time
41
107
 
42
108
  def delete(self, key: str) -> None:
43
- """Remove value from cache."""
109
+ """Remove value from cache.
110
+
111
+ Args:
112
+ key: The cache key to delete.
113
+
114
+ Note:
115
+ If the key doesn't exist, this method does nothing (no error raised).
116
+ """
44
117
  self._cache.pop(key, None)
118
+ self._expiry.pop(key, None)
45
119
 
46
120
  def clear_pattern(self, pattern: str) -> int:
47
- """Clear all keys matching pattern."""
121
+ """Clear all keys matching a glob pattern.
122
+
123
+ Args:
124
+ pattern: A glob pattern to match keys. Supports wildcards:
125
+ - '*' matches any sequence of characters
126
+ - '?' matches any single character
127
+ - '[seq]' matches any character in seq
128
+ - '[!seq]' matches any character not in seq
129
+
130
+ Returns:
131
+ The number of keys that were deleted.
132
+
133
+ Example:
134
+ >>> cache.set("user:123", data1)
135
+ >>> cache.set("user:456", data2)
136
+ >>> cache.set("session:789", data3)
137
+ >>> count = cache.clear_pattern("user:*") # Deletes user:123 and user:456
138
+ >>> print(count) # 2
139
+ """
48
140
  import fnmatch
49
141
 
50
142
  keys_to_delete = [k for k in self._cache.keys() if fnmatch.fnmatch(k, pattern)]
51
143
  for key in keys_to_delete:
52
144
  del self._cache[key]
145
+ self._expiry.pop(key, None)
53
146
  return len(keys_to_delete)
54
147
 
55
148
  def get_stats(self) -> Dict[str, Any]:
56
- """Get cache statistics."""
149
+ """Get cache performance statistics.
150
+
151
+ Returns:
152
+ Dictionary containing:
153
+ - hits: Number of successful cache retrievals
154
+ - misses: Number of cache misses
155
+ - hit_rate: Ratio of hits to total requests (0.0 to 1.0)
156
+ - size: Current number of items in cache
157
+ - max_size: Maximum cache capacity
158
+
159
+ Example:
160
+ >>> stats = cache.get_stats()
161
+ >>> print(f"Cache is {stats['hit_rate']:.2%} effective")
162
+ >>> print(f"Using {stats['size']}/{stats['max_size']} slots")
163
+ """
164
+ total_requests = self._hits + self._misses
57
165
  return {
58
166
  "hits": self._hits,
59
167
  "misses": self._misses,
60
- "hit_rate": self._hits / (self._hits + self._misses) if (self._hits + self._misses) > 0 else 0,
168
+ "hit_rate": self._hits / total_requests if total_requests > 0 else 0.0,
61
169
  "size": len(self._cache),
62
170
  "max_size": self.max_size,
63
171
  }