diff --git a/reporters/python/TODO.md b/reporters/python/TODO.md index 7c67559..dc9790e 100644 --- a/reporters/python/TODO.md +++ b/reporters/python/TODO.md @@ -7,7 +7,7 @@ - [x] Thread safety - [x] Manage exiting - [x] Check circular references -- [ ] Add log method +- [x] Add log method - [ ] Export only what's needed and rename to `trace_that` - [ ] Manage connection health (stale/reopening/error handling) - [ ] Release to pip diff --git a/reporters/python/example.py b/reporters/python/example.py index f2588d9..3ba4e0f 100644 --- a/reporters/python/example.py +++ b/reporters/python/example.py @@ -3,12 +3,12 @@ from trace_that import trace_that @trace_that -def hello(name: str, greeting='Hello') -> str: - time.sleep(1) +async def hello(name: str, greeting='Hello') -> str: + await asyncio.sleep(1) return f'{greeting}, {name}!' async def main(): - hello(name='Kacper', greeting='Yo') + await hello(name='Kacper', greeting='Yo') if __name__ == '__main__': asyncio.run(main()) \ No newline at end of file diff --git a/reporters/python/json_marhsaller.py b/reporters/python/json_marhsaller.py index 744623b..1205d46 100644 --- a/reporters/python/json_marhsaller.py +++ b/reporters/python/json_marhsaller.py @@ -2,15 +2,16 @@ def serialize(obj, seen=None): if seen is None: - seen = {} + seen = set() obj_id = id(obj) if obj_id in seen: # Circular reference detected; return a reference placeholder return "" + seen = seen.copy() # Mark the current object as seen - seen[obj_id] = obj + seen.add(obj_id) # Handle basic data types if isinstance(obj, (str, int, float, bool, type(None))): diff --git a/reporters/python/log_test.py b/reporters/python/log_test.py new file mode 100644 index 0000000..b42bdc8 --- /dev/null +++ b/reporters/python/log_test.py @@ -0,0 +1,23 @@ +from trace_that import create_log +import unittest + +class MemoryReporter: + def __init__(self): + self.messages = [] + + def send(self, msg: str) -> None: + self.messages.append(msg) + +class TestLog(unittest.TestCase): + def test_log(self): + reporter = MemoryReporter() + log = create_log(reporter) + log('hello', {'a': 1}) + + ok_msg = next(m for m in reporter.messages if m['status'] == 'ok') + self.assertIsNotNone(ok_msg) + self.assertEqual(ok_msg['name'], 'hello') + self.assertEqual(ok_msg['details'], {'a': 1}) + +if __name__ == '__main__': + unittest.main() \ No newline at end of file diff --git a/reporters/python/trace_that.py b/reporters/python/trace_that.py index b3f983a..389c5d4 100644 --- a/reporters/python/trace_that.py +++ b/reporters/python/trace_that.py @@ -1,18 +1,26 @@ -from typing import Callable, TypeVar, Type +from typing import Callable, TypeVar, Type, Any from typing_extensions import ParamSpec import time import uuid import inspect from ws_reporter import WebSocketReporter -class Reporter: - def send(self, msg: str) -> None: - pass - T = TypeVar('T') R = TypeVar('R') P = ParamSpec('P') +class TraceThat: + def __init__(self, trace_that_fn: Callable[[Callable[P, R]], Callable[P,R]], log: Callable[[str, Any], None]): + self.trace_that_fn = trace_that_fn + self.log = log + + def __call__(self, func: Callable[P, R]) -> Callable[P, R]: + return self.trace_that_fn(func) + +class Reporter: + def send(self, msg: str) -> None: + pass + def create_trace_that(reporter: Type[Reporter]): def trace_that(func: Callable[P, R]) -> Callable[P, R]: def before(*args: P.args, **kwargs: P.kwargs) -> None: @@ -87,5 +95,24 @@ async def awaiter(): return trace_that +def create_log(reporter: Type[Reporter]): + def log(name: str, msg: any) -> None: + print(f'[trace_that] {name} {msg=}') + call_id = uuid.uuid4().hex + start_time = int(time.time() * 1000) + reporter.send({ + 'status': 'ok', + 'callId': call_id, + 'name': name, + 'startEpochMs': start_time, + 'endEpochMs': start_time, + 'details': msg + }) + + return log + +reporter = WebSocketReporter() +log = create_log(reporter) +trace_that_fun = create_trace_that(reporter) -trace_that = create_trace_that(WebSocketReporter()) \ No newline at end of file +trace_that = TraceThat(trace_that_fun, log)