From 34c96d8cec49ce656aea10f7d34435a69b966a17 Mon Sep 17 00:00:00 2001 From: Rob Nagler <5495179+robnagler@users.noreply.github.com> Date: Sun, 26 Jan 2025 21:31:03 +0000 Subject: [PATCH 1/6] ckp --- pykern/http_unit.py | 199 ++++++++++++++++++++++++++++++++------------ 1 file changed, 147 insertions(+), 52 deletions(-) diff --git a/pykern/http_unit.py b/pykern/http_unit.py index 40908280..a3f18c1c 100644 --- a/pykern/http_unit.py +++ b/pykern/http_unit.py @@ -4,80 +4,175 @@ :license: http://www.apache.org/licenses/LICENSE-2.0.html """ -# Defer imports for unit tests +# Defer as many pykern imports as possible to defer pkconfig runing +from pykern.pkcollections import PKDict +import os +import signal +import time class Setup: + """Usage:: + + async with http_unit.Setup(api_classes=(_class())) as c: + from pykern.pkcollections import PKDict + from pykern import pkunit + + e = PKDict(ping="pong") + pkunit.pkeq(e.pkupdate(counter=1), await c.call_api("echo", e)) + pkunit.pkeq(e.pkupdate(counter=2), await c.call_api("echo", e)) + + May be subclassed to start multiple servers. + """ AUTH_TOKEN = "http_unit_auth_secret" - def __init__(self, api_classes, attr_classes=(), coros=()): - import os, time - from pykern.pkcollections import PKDict + def __init__(self, **server_config): + # Must be first + self._global_config() + self.http_config = self._http_config() + self.server_config = self._server_config(server_config) + self.server_pid = self._server_process() + time.sleep(1) + self.client = self._client() - def _global_config(): - c = PKDict( - PYKERN_PKDEBUG_WANT_PID_TIME="1", - ) - os.environ.update(**c) - from pykern import pkconfig + def destroy(self): + """Destroy client and kill attributes ``*_pid`` attrs""" - pkconfig.reset_state_for_testing(c) + self.client.destroy() + for p in filter(lambda x: x.endswith("_pid"), dir(self)): + try: + os.kill(getattr(self, p), signal.SIGKILL) + except Exception: + pass - def _http_config(): - from pykern import pkconst, pkunit + def _client(self): + """Creates a client to be used for requests. - return PKDict( - # any uri is fine - api_uri="/http_unit", - # just needs to be >= 16 word (required by http) chars; apps should generate this randomly - tcp_ip=pkconst.LOCALHOST_IP, - tcp_port=pkunit.unbound_localhost_tcp_port(), - ) + Called in `__init__`. - def _server(): - from pykern import pkdebug, http + Returns: + object: http client, set to ``self.client`` + """ + from pykern import http, pkdebug - def _api_classes(): - class AuthAPI(http.AuthAPI): - PYKERN_HTTP_TOKEN = self.AUTH_TOKEN + pkdebug.pkdp(self.http_config) + return http.HTTPClient(self.http_config.copy()) - return list(api_classes) + [AuthAPI] + def _client_awaitable(self): + """How to connect to client + + Awaited in `__aenter__`. + + Returns: + Awaitable: coroutine to connect to client + """ - if rv := os.fork(): - return rv - try: - pkdebug.pkdlog("start server") - http.server_start( - attr_classes=attr_classes, - api_classes=_api_classes(), - http_config=self.http_config.copy(), - coros=coros, - ) - except Exception as e: - pkdebug.pkdlog("server exception={} stack={}", e, pkdebug.pkdexc()) - finally: - os._exit(0) - - _global_config() - self.http_config = _http_config() - self.server_pid = _server() - time.sleep(1) from pykern import http - self.client = http.HTTPClient(self.http_config.copy()) + return self.client.connect(http.AuthArgs(token=self.AUTH_TOKEN)) - def destroy(self): - import os, signal + def _global_config(self, **kwargs): + """Initializes os.environ and pkconfig - os.kill(self.server_pid, signal.SIGKILL) + Called first. - async def __aenter__(self): + Args: + kwargs (dict): merged into environ and config (from subclasses) + """ + + c = PKDict( + PYKERN_PKDEBUG_WANT_PID_TIME="1", + **kwargs, + ) + os.environ.update(**c) + from pykern import pkconfig + + pkconfig.reset_state_for_testing(c) + + def _http_config(self): + """Initializes ``self.http_config`` + + Returns: + PKDict: configuration to be shared with client and server + """ + from pykern import pkconst, pkunit + + return PKDict( + # any uri is fine + api_uri="/http_unit", + # just needs to be >= 16 word (required by http) chars; apps should generate this randomly + tcp_ip=pkconst.LOCALHOST_IP, + tcp_port=pkunit.unbound_localhost_tcp_port(), + ) + + def _server_config(self, init_config): + """Config to be passed to `pykern.http.server_start` in `server_start` + + A simple `pykern.http.AuthAPI` implementation is defaulted if not in ``init_config.api_classes``. + + Args: + init_config (dict): what was passed to `__init__` + Returns: + PKDict: configuration for `server_start` + """ + + def _api_classes(init_classes): + from pykern import http + + class AuthAPI(http.AuthAPI): + PYKERN_HTTP_TOKEN = self.AUTH_TOKEN + + rv = init_classes + if any(filter(lambda c: issubclass(c, http.AuthAPI), rv)): + return rv + return rv + [AuthAPI] + + rv = PKDict(init_config) if init_config else PKDict() + rv.pksetdefault( + api_classes=(), + attr_classes=(), + coros=(), + http_config=PKDict, + ) + rv.http_config.pksetdefault(**self.http_config) + rv.api_classes = _api_classes(list(rv.api_classes)) + return rv + + def _server_process(self): + """Call `server_start` in separate process + + Override this method to start multiple servers, saving pids in + attributes that end in ``_pid`` so that `destroy` will kill + them. + + Returns: + int: pid of process + + """ + from pykern import pkdebug + + pkdebug.pkdp(self.server_config) + if rv := os.fork(): + return rv + try: + pkdebug.pkdlog("start server") + self._server_start() + except Exception as e: + pkdebug.pkdlog("{} exception={} stack={}", op, e, pkdebug.pkdexc()) + finally: + os._exit(0) + + def _server_start(self): + """Calls http.server_start with ``self.server_config``""" from pykern import http - await self.client.connect(http.AuthArgs(token=self.AUTH_TOKEN)) + http.server_start(**self.server_config) + + async def __aenter__(self): + await self._client_awaitable() return self.client async def __aexit__(self, *args, **kwargs): - self.client.destroy() + self.destroy() return False From 8b37a96760448cb721b4052b48926a988cf9e742 Mon Sep 17 00:00:00 2001 From: Rob Nagler <5495179+robnagler@users.noreply.github.com> Date: Mon, 27 Jan 2025 00:22:36 +0000 Subject: [PATCH 2/6] pykern/http_unit.py --- pykern/http_unit.py | 12 ++++----- pykern/pkcollections.py | 50 +++++++++++++++++++++++++++++++++++++ pykern/pkinspect.py | 3 --- pykern/pkunit.py | 37 ++++++--------------------- pykern/util.py | 30 ++++++++++++++++++++-- tests/pkcollections_test.py | 35 +++++++++++++++++++++++--- 6 files changed, 122 insertions(+), 45 deletions(-) diff --git a/pykern/http_unit.py b/pykern/http_unit.py index a3f18c1c..eeee3b24 100644 --- a/pykern/http_unit.py +++ b/pykern/http_unit.py @@ -33,7 +33,7 @@ def __init__(self, **server_config): self.http_config = self._http_config() self.server_config = self._server_config(server_config) self.server_pid = self._server_process() - time.sleep(1) + time.sleep(2) self.client = self._client() def destroy(self): @@ -56,8 +56,7 @@ def _client(self): """ from pykern import http, pkdebug - pkdebug.pkdp(self.http_config) - return http.HTTPClient(self.http_config.copy()) + return http.HTTPClient(pkdebug.pkdp(self.http_config.copy())) def _client_awaitable(self): """How to connect to client @@ -96,14 +95,14 @@ def _http_config(self): Returns: PKDict: configuration to be shared with client and server """ - from pykern import pkconst, pkunit + from pykern import pkconst, util return PKDict( # any uri is fine api_uri="/http_unit", # just needs to be >= 16 word (required by http) chars; apps should generate this randomly tcp_ip=pkconst.LOCALHOST_IP, - tcp_port=pkunit.unbound_localhost_tcp_port(), + tcp_port=util.unbound_localhost_tcp_port(), ) def _server_config(self, init_config): @@ -152,14 +151,13 @@ def _server_process(self): """ from pykern import pkdebug - pkdebug.pkdp(self.server_config) if rv := os.fork(): return rv try: pkdebug.pkdlog("start server") self._server_start() except Exception as e: - pkdebug.pkdlog("{} exception={} stack={}", op, e, pkdebug.pkdexc()) + pkdebug.pkdlog("exception={} stack={}", e, pkdebug.pkdexc()) finally: os._exit(0) diff --git a/pykern/pkcollections.py b/pykern/pkcollections.py index 7d384433..8f4aae3c 100644 --- a/pykern/pkcollections.py +++ b/pykern/pkcollections.py @@ -14,6 +14,8 @@ import types import pykern.pkcompat +_READ_ONLY_ATTR = "_PKDict__read_only" + class PKDict(dict): """A subclass of dict that allows items to be read/written as attributes. @@ -46,6 +48,10 @@ class PKDict(dict): only, not general objects. """ + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + super().__setattr__(_READ_ONLY_ATTR, False) + def __delattr__(self, name): raise PKDictNameError("{}: you cannot delete attributes", name) @@ -89,6 +95,7 @@ def pkdel(self, name, default=None): Returns: object: value (if exists) or default """ + self.__assert_not_read_only() try: return self[name] except KeyError: @@ -99,6 +106,14 @@ def pkdel(self, name, default=None): except KeyError: pass + def pkis_read_only(self): + """Tests is read only attribute + + Returns: + True: if attribute is read only + """ + return getattr(self, _READ_ONLY_ATTR) + def pkmerge(self, to_merge, make_copy=True): """Add `to_merge` to `self` @@ -200,6 +215,7 @@ def pknested_set(self, qualifiers, value): Returns: object: self """ + self.__assert_not_read_only() q = qualifiers.split(".") if isinstance(qualifiers, str) else list(qualifiers) d = self for k in q[:-1]: @@ -227,6 +243,7 @@ def pksetdefault(self, *args, **kwargs): Returns: object: self """ + self.__assert_not_read_only() if args and kwargs: raise AssertionError("one of args or kwargs must be set, but not both") if args: @@ -259,6 +276,7 @@ def pksetdefault1(self, *args, **kwargs): object: ``self[key]``; either `value` if just set, or preexisting value """ + self.__assert_not_read_only() if args and kwargs: raise AssertionError("one of args or kwargs must be set, but not both") if args: @@ -289,12 +307,38 @@ def pkunchecked_nested_get(self, qualifiers, default=None): except (KeyError, IndexError, TypeError, ValueError): return default + def __assert_not_read_only(self): + if getattr(self, _READ_ONLY_ATTR): + raise PKDictReadOnlyError() + + def pkset_read_only(self): + """Makes the current instance readonly + + Returns: + PKDict: self + """ + if self.pkis_read_only(): + return self + super().__setattr__(_READ_ONLY_ATTR, True) + for n in ( + "__delitem__", + "__setitem__", + "clear", + "pop", + "popitem", + "setdefault" "update", + ): + super().__setattr__(n, self.__assert_not_read_only) + return self + def pkupdate(self, *args, **kwargs): """Call `dict.update` and return ``self``.""" + self.__assert_not_read_only() super(PKDict, self).update(*args, **kwargs) return self def __pksetdefault_one(self, key, value): + self.__assert_not_read_only() if key not in self: self[key] = value() if callable(value) else value return self[key] @@ -306,6 +350,12 @@ class PKDictNameError(NameError): pass +class PKDictReadOnlyError(RuntimeError): + """Raised when an attempt to write read-only PKDict""" + + pass + + def canonicalize(obj): """Convert to lists and PKDicts for simpler serialization diff --git a/pykern/pkinspect.py b/pykern/pkinspect.py index 9abd700a..b35e2dd7 100644 --- a/pykern/pkinspect.py +++ b/pykern/pkinspect.py @@ -1,11 +1,8 @@ -# -*- coding: utf-8 -*- """Helper functions for to :mod:`inspect`. :copyright: Copyright (c) 2015 RadiaSoft, Inc. All Rights Reserved. :license: http://www.apache.org/licenses/LICENSE-2.0.html """ -from __future__ import absolute_import, division, print_function - # Avoid pykern imports so avoid dependency issues for pkconfig from pykern.pkcollections import PKDict import importlib diff --git a/pykern/pkunit.py b/pykern/pkunit.py index d424b392..b5713a5a 100644 --- a/pykern/pkunit.py +++ b/pykern/pkunit.py @@ -4,23 +4,21 @@ :license: http://www.apache.org/licenses/LICENSE-2.0.html """ +# defer importing pkconfig from pykern import pkcompat from pykern import pkconst from pykern import pkinspect from pykern import pkio - -# defer importing pkconfig -import pykern.pkconst import contextlib import importlib import inspect import json import os import py +import pykern.pkconst +import pykern.util import pytest -import random import re -import socket import subprocess import sys import traceback @@ -279,31 +277,6 @@ def file_eq(expect_path, *args, **kwargs): _FileEq(expect_path, *args, **kwargs) -def unbound_localhost_tcp_port(start=10000, stop=20000): - """Looks for AF_INET SOCK_STREAM port for which bind succeeds - - Args: - start (int): first port [10000] - stop (int): one greater than last port (passed to range) [20000] - Returns: - int: port is available or raises ValueError - """ - - def _check_port(port): - with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: - s.bind((LOCALHOST_IP, int(port))) - return port - - for p in random.sample(range(start, stop), 100): - try: - return _check_port(p) - except Exception: - pass - raise ValueError( - f"unable find port random sample range={start}-{stop} tries=100 ip={LOCALHOST_IP}" - ) - - def is_test_run(): """Running in a test? @@ -505,6 +478,10 @@ def save_chdir_work(is_pkunit_prefix=False, want_empty=True): ) +#: DEPRECATED +unbound_localhost_tcp_port = pykern.util.unbound_localhost_tcp_port + + def work_dir(): """Returns ephemeral work directory, created if necessary. diff --git a/pykern/util.py b/pykern/util.py index 72314d54..e367f771 100644 --- a/pykern/util.py +++ b/pykern/util.py @@ -4,11 +4,10 @@ :license: http://www.apache.org/licenses/LICENSE-2.0.html """ -# Root module: Limit imports to avoid dependency issues +# Root module: avoid global pykern imports import os.path import sys - _ACCEPTABLE_CONTROL_CODE_RATIO = 0.33 _DEFAULT_ROOT = "run" _DEV_ONLY_FILES = ("setup.py", "pyproject.toml") @@ -155,3 +154,30 @@ def random_base62(length=16): r = random.SystemRandom() return "".join(r.choice(pkconst.BASE62_CHARS) for x in range(length)) + + +def unbound_localhost_tcp_port(start=10000, stop=20000): + """Looks for AF_INET SOCK_STREAM port for which bind succeeds + + Args: + start (int): first port [10000] + stop (int): one greater than last port (passed to range) [20000] + Returns: + int: port is available or raises ValueError + """ + import random, socket + from pykern import pkasyncio, pkconst + + def _check_port(port): + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.bind((pkconst.LOCALHOST_IP, int(port))) + return port + + for p in random.sample(range(start, stop), 100): + try: + return _check_port(p) + except Exception: + pass + raise ValueError( + f"unable find port random sample range={start}-{stop} tries=100 ip={pkconst.LOCALHOST_IP}" + ) diff --git a/tests/pkcollections_test.py b/tests/pkcollections_test.py index 4a218f45..ddcb1909 100644 --- a/tests/pkcollections_test.py +++ b/tests/pkcollections_test.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- """pytest for :mod:`pykern.pkcollections` :copyright: Copyright (c) 2015 RadiaSoft LLC. All Rights Reserved. :license: http://www.apache.org/licenses/LICENSE-2.0.html """ -from __future__ import absolute_import, division, print_function + import pytest @@ -87,10 +86,11 @@ def test_dict(): def test_dict_copy(): from pykern.pkcollections import PKDict import copy - from pykern.pkunit import pkeq, pkne + from pykern.pkunit import pkeq, pkne, pkok n = PKDict(a=1, b=PKDict(c=3)) m = copy.copy(n) + pkok(isinstance(m, PKDict), "not pkdict") pkne(id(n), id(m)) pkeq(id(n.b), id(n.b)) m = copy.deepcopy(n) @@ -206,6 +206,35 @@ def test_pkmerge(): pkeq(PKDict(one=PKDict(two=[1, 2], three=PKDict(four=[4], four2=4.2)), five=5), s) +def test_dict_read_only(): + from pykern.pkcollections import PKDict, PKDictReadOnlyError + from pykern import pkunit, pkdebug + import copy + + n = PKDict(a=1, b=PKDict(c=3)) + n.pkset_read_only() + n.b.pkset_read_only() + pkunit.pkok(n.pkis_read_only(), "n is writable") + pkunit.pkok(n.b.pkis_read_only(), "n.b is writable") + m = copy.copy(n) + pkunit.pkok(m.pkis_read_only(), "m is writable") + pkunit.pkok(m.b.pkis_read_only(), "m.b is writable") + m = n.copy() + pkunit.pkok(not m.pkis_read_only(), "m is read only") + pkunit.pkok(m.b.pkis_read_only(), "m.b is writable") + m = copy.deepcopy(m) + pkunit.pkok(not m.pkis_read_only(), "m is read only") + pkunit.pkok(m.b.pkis_read_only(), "m.b is writable") + m = PKDict().pkupdate(n) + m.b = n.b.copy() + pkunit.pkok(not m.pkis_read_only(), "m is read only") + pkunit.pkok(not m.b.pkis_read_only(), "m.b is read only") + with pkunit.pkexcept(PKDictReadOnlyError): + n.pksetdefault1(foo=1) + with pkunit.pkexcept(PKDictReadOnlyError): + del n["b"] + + def test_subclass(): from pykern import pkcollections from pykern.pkunit import pkeq, pkok From 592d6224222cd758638bf6526a00930c680cc028 Mon Sep 17 00:00:00 2001 From: Rob Nagler <5495179+robnagler@users.noreply.github.com> Date: Mon, 27 Jan 2025 00:25:06 +0000 Subject: [PATCH 3/6] reverse pkcollections --- pykern/pkcollections.py | 50 ------------------------------------- tests/pkcollections_test.py | 35 +++----------------------- 2 files changed, 3 insertions(+), 82 deletions(-) diff --git a/pykern/pkcollections.py b/pykern/pkcollections.py index 8f4aae3c..7d384433 100644 --- a/pykern/pkcollections.py +++ b/pykern/pkcollections.py @@ -14,8 +14,6 @@ import types import pykern.pkcompat -_READ_ONLY_ATTR = "_PKDict__read_only" - class PKDict(dict): """A subclass of dict that allows items to be read/written as attributes. @@ -48,10 +46,6 @@ class PKDict(dict): only, not general objects. """ - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - super().__setattr__(_READ_ONLY_ATTR, False) - def __delattr__(self, name): raise PKDictNameError("{}: you cannot delete attributes", name) @@ -95,7 +89,6 @@ def pkdel(self, name, default=None): Returns: object: value (if exists) or default """ - self.__assert_not_read_only() try: return self[name] except KeyError: @@ -106,14 +99,6 @@ def pkdel(self, name, default=None): except KeyError: pass - def pkis_read_only(self): - """Tests is read only attribute - - Returns: - True: if attribute is read only - """ - return getattr(self, _READ_ONLY_ATTR) - def pkmerge(self, to_merge, make_copy=True): """Add `to_merge` to `self` @@ -215,7 +200,6 @@ def pknested_set(self, qualifiers, value): Returns: object: self """ - self.__assert_not_read_only() q = qualifiers.split(".") if isinstance(qualifiers, str) else list(qualifiers) d = self for k in q[:-1]: @@ -243,7 +227,6 @@ def pksetdefault(self, *args, **kwargs): Returns: object: self """ - self.__assert_not_read_only() if args and kwargs: raise AssertionError("one of args or kwargs must be set, but not both") if args: @@ -276,7 +259,6 @@ def pksetdefault1(self, *args, **kwargs): object: ``self[key]``; either `value` if just set, or preexisting value """ - self.__assert_not_read_only() if args and kwargs: raise AssertionError("one of args or kwargs must be set, but not both") if args: @@ -307,38 +289,12 @@ def pkunchecked_nested_get(self, qualifiers, default=None): except (KeyError, IndexError, TypeError, ValueError): return default - def __assert_not_read_only(self): - if getattr(self, _READ_ONLY_ATTR): - raise PKDictReadOnlyError() - - def pkset_read_only(self): - """Makes the current instance readonly - - Returns: - PKDict: self - """ - if self.pkis_read_only(): - return self - super().__setattr__(_READ_ONLY_ATTR, True) - for n in ( - "__delitem__", - "__setitem__", - "clear", - "pop", - "popitem", - "setdefault" "update", - ): - super().__setattr__(n, self.__assert_not_read_only) - return self - def pkupdate(self, *args, **kwargs): """Call `dict.update` and return ``self``.""" - self.__assert_not_read_only() super(PKDict, self).update(*args, **kwargs) return self def __pksetdefault_one(self, key, value): - self.__assert_not_read_only() if key not in self: self[key] = value() if callable(value) else value return self[key] @@ -350,12 +306,6 @@ class PKDictNameError(NameError): pass -class PKDictReadOnlyError(RuntimeError): - """Raised when an attempt to write read-only PKDict""" - - pass - - def canonicalize(obj): """Convert to lists and PKDicts for simpler serialization diff --git a/tests/pkcollections_test.py b/tests/pkcollections_test.py index ddcb1909..4a218f45 100644 --- a/tests/pkcollections_test.py +++ b/tests/pkcollections_test.py @@ -1,9 +1,10 @@ +# -*- coding: utf-8 -*- """pytest for :mod:`pykern.pkcollections` :copyright: Copyright (c) 2015 RadiaSoft LLC. All Rights Reserved. :license: http://www.apache.org/licenses/LICENSE-2.0.html """ - +from __future__ import absolute_import, division, print_function import pytest @@ -86,11 +87,10 @@ def test_dict(): def test_dict_copy(): from pykern.pkcollections import PKDict import copy - from pykern.pkunit import pkeq, pkne, pkok + from pykern.pkunit import pkeq, pkne n = PKDict(a=1, b=PKDict(c=3)) m = copy.copy(n) - pkok(isinstance(m, PKDict), "not pkdict") pkne(id(n), id(m)) pkeq(id(n.b), id(n.b)) m = copy.deepcopy(n) @@ -206,35 +206,6 @@ def test_pkmerge(): pkeq(PKDict(one=PKDict(two=[1, 2], three=PKDict(four=[4], four2=4.2)), five=5), s) -def test_dict_read_only(): - from pykern.pkcollections import PKDict, PKDictReadOnlyError - from pykern import pkunit, pkdebug - import copy - - n = PKDict(a=1, b=PKDict(c=3)) - n.pkset_read_only() - n.b.pkset_read_only() - pkunit.pkok(n.pkis_read_only(), "n is writable") - pkunit.pkok(n.b.pkis_read_only(), "n.b is writable") - m = copy.copy(n) - pkunit.pkok(m.pkis_read_only(), "m is writable") - pkunit.pkok(m.b.pkis_read_only(), "m.b is writable") - m = n.copy() - pkunit.pkok(not m.pkis_read_only(), "m is read only") - pkunit.pkok(m.b.pkis_read_only(), "m.b is writable") - m = copy.deepcopy(m) - pkunit.pkok(not m.pkis_read_only(), "m is read only") - pkunit.pkok(m.b.pkis_read_only(), "m.b is writable") - m = PKDict().pkupdate(n) - m.b = n.b.copy() - pkunit.pkok(not m.pkis_read_only(), "m is read only") - pkunit.pkok(not m.b.pkis_read_only(), "m.b is read only") - with pkunit.pkexcept(PKDictReadOnlyError): - n.pksetdefault1(foo=1) - with pkunit.pkexcept(PKDictReadOnlyError): - del n["b"] - - def test_subclass(): from pykern import pkcollections from pykern.pkunit import pkeq, pkok From 16c0ca358a9a4d291cdf96be52b3b9e1433b006d Mon Sep 17 00:00:00 2001 From: Rob Nagler <5495179+robnagler@users.noreply.github.com> Date: Mon, 27 Jan 2025 02:09:24 +0000 Subject: [PATCH 4/6] improve logging --- pykern/http.py | 4 ++-- pykern/http_unit.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pykern/http.py b/pykern/http.py index 8dfc5cb8..c721e0e7 100644 --- a/pykern/http.py +++ b/pykern/http.py @@ -493,7 +493,7 @@ def _reply(call, obj): r.call_id = call.call_id self.handler.write_message(_pack_msg(r), binary=True) except Exception as e: - pkdlog("exception={} call={} stack={}", call, e, pkdexc()) + pkdlog("exception={} call={} stack={}", e, call, pkdexc()) self.destroy() def _quest_kwargs(): @@ -507,7 +507,7 @@ def _quest_kwargs(): self._log("error", None, "msg unpack error={}", [e]) self.destroy() return None - self._log("call", c) + self._log("call", c, "api={}", [c.api_name]) if not (a := _api(c)): return r = await _call(c, a, c.api_args) diff --git a/pykern/http_unit.py b/pykern/http_unit.py index eeee3b24..6cf24b28 100644 --- a/pykern/http_unit.py +++ b/pykern/http_unit.py @@ -54,9 +54,9 @@ def _client(self): Returns: object: http client, set to ``self.client`` """ - from pykern import http, pkdebug + from pykern import http - return http.HTTPClient(pkdebug.pkdp(self.http_config.copy())) + return http.HTTPClient(self.http_config.copy()) def _client_awaitable(self): """How to connect to client From eedb99a433be3440e8980c8a669dc73453637ae6 Mon Sep 17 00:00:00 2001 From: Rob Nagler <5495179+robnagler@users.noreply.github.com> Date: Mon, 27 Jan 2025 02:13:07 +0000 Subject: [PATCH 5/6] fmt --- pykern/pkinspect.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pykern/pkinspect.py b/pykern/pkinspect.py index b35e2dd7..77735c1b 100644 --- a/pykern/pkinspect.py +++ b/pykern/pkinspect.py @@ -3,6 +3,7 @@ :copyright: Copyright (c) 2015 RadiaSoft, Inc. All Rights Reserved. :license: http://www.apache.org/licenses/LICENSE-2.0.html """ + # Avoid pykern imports so avoid dependency issues for pkconfig from pykern.pkcollections import PKDict import importlib From 96caac2707aba888f9d1717ffd5043678403ebae Mon Sep 17 00:00:00 2001 From: Rob Nagler <5495179+robnagler@users.noreply.github.com> Date: Mon, 27 Jan 2025 02:18:23 +0000 Subject: [PATCH 6/6] xlsx test fix due to .00 vs no ".00" version xlsxwriter 3.2.0 to 3.2.1 --- tests/xlsx_data/1.out/xl/worksheets/sheet1.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/xlsx_data/1.out/xl/worksheets/sheet1.xml b/tests/xlsx_data/1.out/xl/worksheets/sheet1.xml index e6552e44..705823e2 100644 --- a/tests/xlsx_data/1.out/xl/worksheets/sheet1.xml +++ b/tests/xlsx_data/1.out/xl/worksheets/sheet1.xml @@ -1,2 +1,2 @@ -37507.7713.142345ROUND(PRODUCT(100--B3,1),2)135.3435.34ROUND(MOD(B3,1),2)0.34IF(B3<=2,"red","green")greenROUND(MAX(999),2)999.00ROUND(MAX(111,222),2)222.00ROUND(MIN(333,444),2)333.00ROUND(IF(0,1/0,99),2)99.00AND(TRUE,1<=2)TRUEOR(FALSE,3>4)FALSENOT(6<=7)FALSE6789 \ No newline at end of file +375.0007.7713.142345ROUND(PRODUCT(100--B3,1),2)135.3435.34ROUND(MOD(B3,1),2)0.34IF(B3<=2,"red","green")greenROUND(MAX(999),2)999.00ROUND(MAX(111,222),2)222.00ROUND(MIN(333,444),2)333.00ROUND(IF(0,1/0,99),2)99.00AND(TRUE,1<=2)TRUEOR(FALSE,3>4)FALSENOT(6<=7)FALSE6789 \ No newline at end of file