whatap-python 2.1.2__py3-none-any.whl → 2.1.3__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.
whatap/__init__.py CHANGED
@@ -389,6 +389,25 @@ def hooks(home):
389
389
  'mod',
390
390
  key)}
391
391
 
392
+ # 서비스(트랜잭션) 진입점 자동 래핑 (conf 방법). hook_method_patterns 와 같은
393
+ # import-hook 플러밍, 래퍼만 트랜잭션 open/close (whatap.trace.mod.hook_service_patterns).
394
+ # @method_profiling 을 소스 수정 없이 conf 로 적용하는 경로.
395
+ if conf.hook_service_patterns:
396
+ for pattern in conf.hook_service_patterns.split(','):
397
+ pattern = pattern.strip()
398
+ if not pattern or ':' not in pattern:
399
+ continue
400
+ module_name, class_def = pattern.split(':', 1)
401
+ module_name = module_name.strip()
402
+ class_def = class_def.strip()
403
+ if not PLUGIN.get(module_name):
404
+ PLUGIN[module_name] = []
405
+ PLUGIN[module_name].append(class_def)
406
+ # 대상 모듈이 PLUGIN 에 등록되면 ImportFinder 가 class_defs 패키징.
407
+ IMPORT_HOOKS[module_name] = {
408
+ 'def': 'instrument_service',
409
+ 'module': 'whatap.trace.mod.hook_service_patterns'}
410
+
392
411
  if conf.standalone_enabled:
393
412
  if conf.standalone_type == 'multiple-transaction':
394
413
  from whatap.trace.mod.standalone.multiple import instrument_standalone_multiple
@@ -644,6 +663,7 @@ def go(batch=False, opts={}, llm=False):
644
663
 
645
664
 
646
665
  import signal
666
+ import functools
647
667
 
648
668
  from whatap.trace.trace_handler import trace_handler
649
669
  from whatap.trace.trace_error import interceptor_step_error
@@ -671,7 +691,90 @@ def register_app(fn):
671
691
  return trace
672
692
 
673
693
 
694
+ def _service_trace(fn, service_name=None):
695
+ """fn 을 하나의 트랜잭션(TX_START~TX_END)으로 감싼 래퍼를 반환한다.
696
+
697
+ ``@method_profiling`` 데코레이터와 conf ``hook_service_patterns``
698
+ (whatap.trace.mod.hook_service_patterns) 의 **공용 코어**. 여기엔 agent() 부팅이
699
+ 없다 (데코레이터/부트 경로가 각자 처리).
700
+
701
+ 코루틴 함수는 동기 래퍼로 감싸면 트랜잭션이 start_interceptor → 코루틴 생성 →
702
+ end_interceptor 로 즉시 열렸다 닫히고, 실제 본문은 호출자가 await 할 때 트랜잭션
703
+ 밖에서 실행된다. async def 래퍼로 await 경계 안에서 트랜잭션을 열고 닫는다.
704
+ """
705
+ import inspect
706
+
707
+ name = service_name or fn.__name__
708
+
709
+ # 트랜잭션 종료 후 컨텍스트 정리. end_interceptor 는 TX_END 패킷만 보내고
710
+ # entry 맵/로컬 컨텍스트를 비우지 않는다(프레임워크 훅은 별도로 end 를 호출함).
711
+ # 스스로 트랜잭션을 열고 닫으므로 여기서 직접 정리한다:
712
+ # - entry 맵에서 제거(안 하면 활성 트랜잭션 카운트/메모리 누수)
713
+ # - 부모가 있으면(중첩 호출) 복원, 없으면(루트) 로컬 컨텍스트 클리어
714
+ # (안 하면 종료된 ctx 를 계속 가리켜 이후 스텝이 죽은 트랜잭션에 붙음)
715
+ def _finish(ctx, parent):
716
+ try:
717
+ end_interceptor(ctx=ctx)
718
+ if ctx:
719
+ TraceContextManager.end(ctx.id)
720
+ finally:
721
+ if parent:
722
+ TraceContextManager.setLocalContext(parent)
723
+ else:
724
+ TraceContextManager.clearLocalContext()
725
+
726
+ # 감싼 함수의 예외는 기록만 하고 반드시 다시 던진다. 과거엔 예외를 삼키고 None 을
727
+ # 반환해, 호출자가 예외로 에러를 판별하는 코드(예: ASGI 핸들러가 500 을 내보내는 경로)
728
+ # 를 망가뜨렸다(200 + null 응답). 계측 기록 자체가 실패해도 원본 예외를 가리지 않도록
729
+ # 기록부는 감싼다. finally 에서 return 하면 예외가 억제되므로 정리만 한다.
730
+ def _record_error(ctx, e, args, kwargs):
731
+ try:
732
+ interceptor_step_error(e, ctx=ctx)
733
+ if ctx:
734
+ interceptor_error_log(ctx.id, e, fn, args, kwargs)
735
+ logging.debug('WHATAP(@method_profiling): ' + str(e),
736
+ extra={'id': 'WA776'}, exc_info=True)
737
+ except Exception:
738
+ pass
739
+
740
+ if inspect.iscoroutinefunction(fn):
741
+ @functools.wraps(fn)
742
+ async def trace(*args, **kwargs):
743
+ parent = TraceContextManager.getLocalContext()
744
+ ctx = TraceContext()
745
+ try:
746
+ ctx.service_name = name
747
+ start_interceptor(ctx)
748
+ return await fn(*args, **kwargs)
749
+ except Exception as e:
750
+ _record_error(ctx, e, args, kwargs)
751
+ raise
752
+ finally:
753
+ _finish(ctx, parent)
754
+
755
+ return trace
756
+
757
+ @functools.wraps(fn)
758
+ def trace(*args, **kwargs):
759
+ parent = TraceContextManager.getLocalContext()
760
+ ctx = TraceContext()
761
+ try:
762
+ ctx.service_name = name
763
+ start_interceptor(ctx)
764
+ return fn(*args, **kwargs)
765
+ except Exception as e:
766
+ _record_error(ctx, e, args, kwargs)
767
+ raise
768
+ finally:
769
+ _finish(ctx, parent)
770
+
771
+ return trace
772
+
773
+
674
774
  def method_profiling(fn):
775
+ # NOTE: 의도적으로 _service_trace 를 쓰지 않는다. 이 데코레이터는 이미 배포된
776
+ # 공개 API 라, 예외 재전파/컨텍스트 정리/async 지원으로 바꾸면 기존 사용자의
777
+ # 동작이 달라진다. 해당 수정은 dev 에 있고 정식 머지 때 함께 들어온다.
675
778
  def trace(*args, **kwargs):
676
779
  callback = None
677
780
  try:
@@ -689,10 +792,10 @@ def method_profiling(fn):
689
792
  ctx = TraceContextManager.getLocalContext()
690
793
  end_interceptor(ctx=ctx)
691
794
  return callback
692
-
795
+
693
796
  if not os.environ.get('whatap.enabled'):
694
797
  agent()
695
-
798
+
696
799
  return trace
697
800
 
698
801
 
whatap/build.py CHANGED
@@ -1,4 +1,4 @@
1
1
  app = 'Python'
2
2
  name = 'whatap-python'
3
- version = '2.1.2'
4
- release_date = '20260722'
3
+ version = '2.1.3'
4
+ release_date = '20260805'
@@ -37,6 +37,10 @@ Configuration = {
37
37
  "profile_http_parameter_enabled": False,
38
38
  "profile_sql_param_enabled": False,
39
39
  "hook_method_patterns": "",
40
+ # 트랜잭션(서비스) 진입점으로 자동 래핑할 메서드 패턴 (module:Class.method, 콤마 구분).
41
+ # 해당 메서드 호출이 하나의 트랜잭션이 된다(= @method_profiling 의 conf 버전).
42
+ # 프레임워크 자동계측이 안 되는 진입점용. hook_service_patterns 모듈 참조.
43
+ "hook_service_patterns": "",
40
44
 
41
45
  "mtrace_rate": 10,
42
46
  "mtrace_spec": '',
@@ -7,7 +7,11 @@ from whatap.trace.trace_context_manager import TraceContextManager
7
7
 
8
8
 
9
9
  def instrument(module):
10
- root = module.http.root
10
+ # module is odoo.http (hooked as 'odoo.http' so that `root`/`HttpDispatcher`
11
+ # already exist when the hook fires; on Odoo 18+ `odoo.http` is no longer
12
+ # imported during `import odoo`, so hooking the top-level package fired too
13
+ # early and silently failed).
14
+ root = module.root
11
15
  AppClass = root.__class__
12
16
  original_call = AppClass.__call__
13
17
 
@@ -37,27 +41,37 @@ def instrument(module):
37
41
  AppClass.__call__ = wrapper(original_call)
38
42
 
39
43
 
40
- """
41
- Odoo 16+ HttpDispatcher.handle_error 패치
42
44
  """
43
- Dispatcher = getattr(module.http, 'HttpDispatcher', None)
44
-
45
- if Dispatcher:
46
- original_handle_error = Dispatcher.handle_error
45
+ HTTP 요청 경로의 예외 단일 처리 지점을 후킹한다.
46
+ - Odoo 16+ : HttpDispatcher.handle_error(self, exc)
47
+ - Odoo 14/15 : HttpRequest._handle_exception(self, exception)
48
+ (dispatcher 구조 도입 이전이라 HttpDispatcher 가 없다.
49
+ 두 메서드 모두 시그니처가 (self, <exc>) 라 래퍼를 공유한다.)
50
+ 실제 응답 status 는 custom_start_response 가 이후에 갱신하므로, 여기서 넘기는
51
+ 500 은 interceptor_error 의 에러 기록 트리거 용도로만 사용된다.
52
+ """
53
+ def error_wrapper(fn):
54
+ @trace_handler(fn)
55
+ def trace(self, exception):
56
+ ctx = TraceContextManager.getLocalContext()
57
+ interceptor_error(
58
+ 500, # 해당 status 값은 에러 처리용으로만 사용됩니다.
59
+ [exception.__class__.__name__,
60
+ str(exception)],
61
+ ctx=ctx
62
+ )
47
63
 
48
- def wrapper(fn):
49
- @trace_handler(fn)
50
- def trace(self,exception):
51
- ctx = TraceContextManager.getLocalContext()
52
- interceptor_error(
53
- 500, # 해당 status 값은 에러 처리용으로만 사용됩니다.
54
- [exception.__class__.__name__,
55
- str(exception)],
56
- ctx=ctx
57
- )
64
+ callback = fn(self, exception)
65
+ return callback
58
66
 
59
- callback = fn(self, exception)
60
- return callback
67
+ return trace
61
68
 
62
- return trace
63
- Dispatcher.handle_error = wrapper(Dispatcher.handle_error)
69
+ Dispatcher = getattr(module, 'HttpDispatcher', None)
70
+ if Dispatcher:
71
+ # Odoo 16+
72
+ Dispatcher.handle_error = error_wrapper(Dispatcher.handle_error)
73
+ else:
74
+ # Odoo 14/15
75
+ HttpRequest = getattr(module, 'HttpRequest', None)
76
+ if HttpRequest and hasattr(HttpRequest, '_handle_exception'):
77
+ HttpRequest._handle_exception = error_wrapper(HttpRequest._handle_exception)
@@ -0,0 +1,72 @@
1
+ """conf ``hook_service_patterns`` — 지정 메서드를 트랜잭션(서비스) 진입점으로 자동 래핑.
2
+
3
+ whatap.conf 예:
4
+
5
+ hook_service_patterns=myapp.infer:RunwayModel.predict, myapp.jobs:run_batch
6
+
7
+ 각 패턴 ``module:Class.method`` / ``module:func`` / ``module:Class.*`` / ``module:*.*``
8
+ 의 메서드를 import 시점에 ``@method_profiling`` 과 동일하게 감싼다. 그 메서드 호출이
9
+ 곧 하나의 트랜잭션(TX_START~TX_END)이 된다. raw ASGI·커스텀 워커처럼 프레임워크
10
+ 자동계측이 안 되는 진입점을 **소스 수정 없이** 트랜잭션으로 잡는 선언적 경로.
11
+
12
+ 계열 비교 (모두 동일한 import-hook 플러밍, 래퍼만 다름):
13
+ - hook_method_patterns → 메서드를 기존 트랜잭션 안의 **스텝**(TX_METHOD) 으로
14
+ - workflow_method_patterns → 메서드를 LLM **워크플로우** 경계로
15
+ - hook_service_patterns → 메서드를 **트랜잭션** 진입점으로 (이 모듈)
16
+
17
+ 대상 모듈이 PLUGIN 에 class_defs 를 등록하면 ImportFinder 가 그 목록을 패키징해
18
+ 넘겨준다 (trace_import.load_module).
19
+ """
20
+ import inspect
21
+
22
+ from whatap.trace import get_dict
23
+ from whatap import logging
24
+
25
+
26
+ def instrument_service(module_dict):
27
+ """module_dict = {'module': <module>, 'class_defs': [<class.method ...>]}."""
28
+ # 트랜잭션 래핑 코어(@method_profiling 과 공용). 예외 재전파 + 컨텍스트 정리 포함.
29
+ from whatap import _service_trace
30
+
31
+ try:
32
+ module = module_dict['module']
33
+ for class_def in module_dict['class_defs']:
34
+ parts = class_def.split('.')
35
+
36
+ # 대상 클래스/모듈 목록 + 타깃 메서드명 해석 (instrument_plugin 과 동일 규칙).
37
+ if len(parts) == 2:
38
+ if parts[0] == '*':
39
+ m_list = [
40
+ getattr(module, attr) for attr in dir(module)
41
+ if not attr.startswith('__')
42
+ and inspect.isclass(getattr(module, attr))
43
+ and getattr(getattr(module, attr), '__module__', None) == module.__name__
44
+ ]
45
+ else:
46
+ m_list = [getattr(module, parts[0])]
47
+ target = parts[1]
48
+ else:
49
+ m_list = [module]
50
+ target = parts[0]
51
+
52
+ for m in m_list:
53
+ if target == '*':
54
+ t_list = [
55
+ attr for attr in dir(m)
56
+ if not attr.startswith('__')
57
+ and hasattr(m, attr)
58
+ and inspect.isfunction(getattr(m, attr))
59
+ ]
60
+ else:
61
+ t_list = [target]
62
+
63
+ for t in t_list:
64
+ if not hasattr(m, t):
65
+ continue
66
+ wrapped = _service_trace(getattr(m, t))
67
+ try:
68
+ setattr(m, t, wrapped)
69
+ except Exception:
70
+ get_dict(m)[t] = wrapped
71
+ except Exception as e:
72
+ logging.debug(e, extra={'id': 'hook_service_patterns ERROR'})
@@ -177,6 +177,17 @@ class TraceContextManager(object):
177
177
  cls.local.context = None
178
178
  cls.entry.remove(key)
179
179
 
180
+ @classmethod
181
+ def clearLocalContext(cls):
182
+ """현재 로컬 컨텍스트를 비운다 — threading.local + contextvars 양쪽.
183
+
184
+ 복원할 부모가 없는 **루트 트랜잭션 종료**용. ``setLocalContext(None)`` 은 None 에
185
+ thread 속성을 세팅하다 터지고, ``end()`` 는 contextvars(py3.7+ getLocalContext 가
186
+ 우선 참조) 를 비우지 않아 컨텍스트가 누수된다. 둘 다 안전하게 None 으로 만든다."""
187
+ if contextvars:
188
+ cls.whatap_coroutine_context.set(None)
189
+ cls.local.context = None
190
+
180
191
  @classmethod
181
192
  def getTxProfile(cls, n):
182
193
  ctx = cls.getLocalContext()
@@ -24,7 +24,11 @@ def load_module(module, fullname):
24
24
  __import__(wfullname)
25
25
  wmodule = sys.modules.get(wfullname)
26
26
 
27
- if wfullname.endswith('plugin'):
27
+ # 대상 모듈이 PLUGIN 에 class_defs 를 등록했으면(hook_method_patterns /
28
+ # workflow_method_patterns / hook_service_patterns 계열) 패턴 목록을 함께
29
+ # 넘긴다. 과거엔 인스트루먼트 모듈명이 'plugin' 으로 끝나는지로 판별했으나,
30
+ # 파일명 규칙과 디커플하기 위해 PLUGIN 등록 여부로 판별한다.
31
+ if fullname in PLUGIN:
28
32
  module = {'module': module, 'class_defs': PLUGIN[fullname]}
29
33
 
30
34
  getattr(wmodule,
@@ -151,6 +151,6 @@ DEFINITION = {
151
151
  ('frappe.app', 'instrument'),
152
152
  ],
153
153
  'application.odoo': [
154
- ('odoo', 'instrument'),
154
+ ('odoo.http', 'instrument'),
155
155
  ],
156
156
  }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: whatap-python
3
- Version: 2.1.2
3
+ Version: 2.1.3
4
4
  Summary: Monitoring and Profiling Service
5
5
  Home-page: https://www.whatap.io
6
6
  Author: whatap
@@ -1,8 +1,8 @@
1
1
  whatap/LICENSE,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
2
  whatap/README.rst,sha256=M4dohffaOQQcSlo02OgWplEr9SlffyjV3v7w9vHf1RI,967
3
- whatap/__init__.py,sha256=tuUaSBg5PFasSmn3wUJASnPccQnSa0xOp0xq5BEdMLQ,32665
3
+ whatap/__init__.py,sha256=DCn0GyYmoWQEE_pa6789c8imLEKDLP0HGosBa8B38Rs,37631
4
4
  whatap/__main__.py,sha256=9fgK-V2eh7rZQ0OKWjpYsWRCyhOx35zRK6XUvJXWuUo,64
5
- whatap/build.py,sha256=agjw2Vrk6AO4IA0mFeSpzCno4JLLCM3mtXyhi9ttD3w,82
5
+ whatap/build.py,sha256=TVTZ23Le9Q3KY0287mXCzBULq6fwVPzLwhnne_Vj164,82
6
6
  whatap/whatap.conf,sha256=E3UACbCDQ3-3XgDJWovbAtEN6LVLa7BQuCA0NXtX3CY,457
7
7
  whatap/agent/darwin/amd64/whatap_python,sha256=dLdQ2aVjuinboro4S8600b4sEA73RpFIHw72lvkSvRk,20802384
8
8
  whatap/agent/darwin/arm64/whatap_python,sha256=ar2scLqop8rVxB_ITdOrSIMlyTThQ6rF1V6pf1acqag,19784050
@@ -12,7 +12,7 @@ whatap/agent/windows/whatap_python.exe,sha256=ZmZIlcLD8yecsTOlqR6SBnLVQvlbtN70Wv
12
12
  whatap/bootstrap/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
13
  whatap/bootstrap/sitecustomize.py,sha256=snxCliLZwT8oel1rLL6-F-mQ9nYeDFCA5WQV97ijwTM,505
14
14
  whatap/conf/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
- whatap/conf/configuration.py,sha256=LcO7UemiLWmKSPqP234yjUUTRRnxGUnigMv4KXVPTyg,11607
15
+ whatap/conf/configuration.py,sha256=SFZQSOTtrItPZrznDv48ticTgPFDb9rTDmCM0FnLAQg,11962
16
16
  whatap/conf/configure.py,sha256=tGLj1Yv2RInRviTYdjA9l0QGZvkJ1NTBV_-HoNXGfPQ,3534
17
17
  whatap/conf/license.py,sha256=5Hzi2uZDYAVNkoquL5Jg7f6afvynfyeXJghUHKaMegQ,1320
18
18
  whatap/control/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -119,12 +119,13 @@ whatap/scripts/__init__.py,sha256=WWrf0GvfhO9AYGfQkhtjjrQ1NEkFv5kcFCT6YwJOo3Q,68
119
119
  whatap/trace/__init__.py,sha256=1UfEhctKqrEwFa6-MXr996ZHEGXQsgdWlXbX_YQdWF8,289
120
120
  whatap/trace/simple_trace_context.py,sha256=TrpXyKbEpMcLawLC-QymsRkxxDwPhwVYcCkszxvjFl0,646
121
121
  whatap/trace/trace_context.py,sha256=d-nSjiBfePm6TUzbhWCuK7Ufdx0d5H8c_5TrII7VvXU,6244
122
- whatap/trace/trace_context_manager.py,sha256=_VdYSv5L-q5yFCs46duRuwnRLYtT16k_dXfdXCQ9DVs,7371
122
+ whatap/trace/trace_context_manager.py,sha256=SyJxzLfJIl_FjqeV93PvhKaKQHogvTHz-buBk_n-B3g,7955
123
123
  whatap/trace/trace_error.py,sha256=znlXoJG9aSWAJyavg1nkBbewy6pgKvnaKVFOEaHt2kw,2417
124
124
  whatap/trace/trace_handler.py,sha256=wxKfNyLbq8dAq9Oh6VZM2D4FTtB85_3mhdLPAagdLc0,2951
125
- whatap/trace/trace_import.py,sha256=wlBN5INEHlmELL1KnySzPurTjVvAuRGl5c6TXUGlm2c,2549
126
- whatap/trace/trace_module_definition.py,sha256=aEeSdrO9ng1sZZvZiZL6XsEQ1xgTIZnJEe_Pdovxw94,4616
125
+ whatap/trace/trace_import.py,sha256=rP19Zir4RPtBD0VeKuEjaapaAY-uxSzlDl57zvq-hLU,2941
126
+ whatap/trace/trace_module_definition.py,sha256=lFZ4E_X-T4bkyDASknIkixWjwQArHREKC7_smZnSciY,4621
127
127
  whatap/trace/mod/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
128
+ whatap/trace/mod/hook_service_patterns.py,sha256=iuBC7I4zKuZlT8ECEMzdntgO41rzwEW1OhyBnJ1UQfg,3141
128
129
  whatap/trace/mod/logging.py,sha256=9IOrU_4eo9UqMbWSf1aGIVpaOtiMVuW_ISL87EDxh10,5329
129
130
  whatap/trace/mod/plugin.py,sha256=TAynRvaf-uS4xPZW8ct1wx9-BpPLQ4LBZzBhHw6vG0o,3396
130
131
  whatap/trace/mod/amqp/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -141,7 +142,7 @@ whatap/trace/mod/application/flask.py,sha256=UWBFnxJzh8ogBLH-kjSc6HtVDo09KX4j-y7
141
142
  whatap/trace/mod/application/frappe.py,sha256=Y7iaTX1nIRsRg4Pz2ai5k5JeLMFr4-3QXi1KE_2x19w,8027
142
143
  whatap/trace/mod/application/graphql.py,sha256=9q5TCGJkEGQa7iCqXA8byoiV4u-gWLLDz6PRK8Xu1gQ,6046
143
144
  whatap/trace/mod/application/nameko.py,sha256=xZBw7vUKCdsrSkHoygQtE00syh-4hRuOXJHDazbvRfc,1370
144
- whatap/trace/mod/application/odoo.py,sha256=zWnKCYTjdhMhrrFNrKPjD1sDwskEy2EpsojHbrpS8EM,2194
145
+ whatap/trace/mod/application/odoo.py,sha256=PKInvfxElQ1xJpRAUDms2key1RbDY_S_HOue7z-m4R0,3155
145
146
  whatap/trace/mod/application/starlette.py,sha256=_BfYn8ipFOgOvxKcpz4MQZVlQXyMty8gqol1XfGlFzg,4352
146
147
  whatap/trace/mod/application/tornado.py,sha256=nfY6DzFReiBon3oIbv8VdTa5_SuUtw_pNJp4Jusg5kk,5513
147
148
  whatap/trace/mod/application/wsgi.py,sha256=Wgzextf3RJx2yQXTQ6ebTDCadM9zkuwx-qVJUU-ByGo,6274
@@ -220,8 +221,8 @@ whatap/value/text_hash_value.py,sha256=WPsSWzkR17plw3sgGjhJK0heCQiy0XdDYhawvBvQl
220
221
  whatap/value/text_value.py,sha256=8CWF57POGgosr5zEnGc0BdY0pYF0Vh8EsifHaRgfEwU,1148
221
222
  whatap/value/value.py,sha256=puRYlGm7q0iX3TKOqe_e0NdgK9Zj2J1t7NzqaQ8g7bU,578
222
223
  whatap/value/value_enum.py,sha256=AoUU4HUWC1bPan8k8CRBE9ayl0rGvJJHdRCTsamkWxE,2577
223
- whatap_python-2.1.2.dist-info/METADATA,sha256=s74-e4_j4gNIp9kCJHaV0xQIv5bVQAT3TAyFB1UFsPc,2244
224
- whatap_python-2.1.2.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
225
- whatap_python-2.1.2.dist-info/entry_points.txt,sha256=lAwpO3oqmPt5riREZO759dHWoM8dqv-vMpDWwDcMiJY,280
226
- whatap_python-2.1.2.dist-info/top_level.txt,sha256=8bRV-cNhEi4cSEIRQm5zDLRqkH1ki9YVrcfLIUVs5jg,7
227
- whatap_python-2.1.2.dist-info/RECORD,,
224
+ whatap_python-2.1.3.dist-info/METADATA,sha256=UZhX-UuYdHi2y92D90KF_nQlPzOSBUV8A8DSGH4Q7tQ,2244
225
+ whatap_python-2.1.3.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
226
+ whatap_python-2.1.3.dist-info/entry_points.txt,sha256=lAwpO3oqmPt5riREZO759dHWoM8dqv-vMpDWwDcMiJY,280
227
+ whatap_python-2.1.3.dist-info/top_level.txt,sha256=8bRV-cNhEi4cSEIRQm5zDLRqkH1ki9YVrcfLIUVs5jg,7
228
+ whatap_python-2.1.3.dist-info/RECORD,,