Repository: radiasoft/pykern

Issue:
pkconfig.in_dev_mode() to replace most channel_in("dev") uses
We have used `channel_in("dev")` to mean both "executing in a development environment" and "enable development features". These are two different modes, and they need to be controlled differently. 

`$PKERN_PKCONFIG_DEV_MODE` config will control `pkconfig.in_dev_mode()`. It will default to be true if the channel is "dev" so there won't be any change in behavior.

For example, in development, you want to enable [debug mode in Tornado in pkasyncio](https://github.com/radiasoft/pykern/blob/5cac3be0e2b66e332e5dcdba413281c264303ddf/pykern/pkasyncio.py#L90):

```py
        debug=(pkconfig.channel_in("dev"), bool, "enable debugging for asyncio"),
```

However, in a dev execution environment that is simulating deployment, we would not want this on, and this default should be controlled by `in_dev_mode`.




Discussion:

Patch:
diff --git a/pykern/pkasyncio.py b/pykern/pkasyncio.py
index def4ac8..19611de 100644
--- a/pykern/pkasyncio.py
+++ b/pykern/pkasyncio.py
@@ -12,11 +12,12 @@ import tornado.httpserver
 import tornado.ioloop
 import tornado.web
 
-cfg = None
+_cfg = None
 
 
 class Loop:
     def __init__(self):
+        _init()
         self._coroutines = []
 
     def http_server(self, http_cfg):
@@ -37,12 +38,12 @@ class Loop:
 
         async def _do():
             # TODO(e-carlin): pull in the one in job_supervisor.py
-            p = http_cfg.get("tcp_port", cfg.server_port)
-            i = http_cfg.get("tcp_ip", cfg.server_ip)
+            p = http_cfg.get("tcp_port", _cfg.server_port)
+            i = http_cfg.get("tcp_ip", _cfg.server_ip)
             tornado.httpserver.HTTPServer(
                 tornado.web.Application(
                     http_cfg.uri_map,
-                    debug=http_cfg.get("debug", pkconfig.channel_in("dev")),
+                    debug=http_cfg.get("debug", _cfg.debug),
                 ),
                 xheaders=True,
             ).listen(p, i)
@@ -67,7 +68,7 @@ class Loop:
             await asyncio.gather(*self._coroutines)
 
         assert self._coroutines, "nothing to await"
-        asyncio.run(_do(), debug=cfg.debug)
+        asyncio.run(_do(), debug=_cfg.debug)
 
 
 async def sleep(secs):
@@ -84,14 +85,11 @@ def _cfg_port(value):
 
 
 def _init():
-    global cfg
-    if cfg:
+    global _cfg
+    if _cfg:
         return
-    cfg = pkconfig.init(
-        debug=(pkconfig.channel_in("dev"), bool, "enable debugging for asyncio"),
+    _cfg = pkconfig.init(
+        debug=(pkconfig.in_dev_mode(), bool, "enable debugging for asyncio"),
         server_ip=("127.0.0.1", str, "ip to listen on"),
         server_port=("9001", _cfg_port, "port to listen on"),
     )
-
-
-_init()
diff --git a/pykern/pkcli/github.py b/pykern/pkcli/github.py
index 11722ba..c09a9e8 100644
--- a/pykern/pkcli/github.py
+++ b/pykern/pkcli/github.py
@@ -561,7 +561,7 @@ def _cfg():
     n = None
     p = PKDict(
         api_pause_seconds=(
-            0 if pkconfig.channel_in("dev") else 10,
+            0 if pkconfig.in_dev_mode() else 10,
             int,
             "pauses between backups",
         ),
@@ -573,7 +573,7 @@ def _cfg():
         ),
         password=[None, str, "github passsword"],
         test_mode=(
-            pkconfig.channel_in("dev"),
+            pkconfig.in_dev_mode(),
             pkconfig.parse_bool,
             f"only backs up {_TEST_REPO} repo",
         ),
diff --git a/pykern/pkconfig.py b/pykern/pkconfig.py
index 08d1fca..994d561 100644
--- a/pykern/pkconfig.py
+++ b/pykern/pkconfig.py
@@ -78,7 +78,7 @@ from pykern import pkinspect
 #: Python version independent value of string instance check
 STRING_TYPES = pkconst.STRING_TYPES
 
-#: Environment variable holding channel (defaults to 'dev')
+#: Environment variable holding channel (defaults to "dev")
 CHANNEL_ATTR = "pykern_pkconfig_channel"
 
 _KEY_PATTERN = "^[A-Z][A-Z0-9_]*[A-Z0-9]$"
@@ -178,9 +178,10 @@ class Required(tuple, object):
 
 
 class RequiredUnlessDev(tuple, object):
-    """Container for a required parameter declaration
+    """Container for a required parameter declaration only `in_dev_mode`.
+
+    Only required if `in_dev_mode` is true.
 
-    Only required in dev
     Example::
 
         cfg = pkconfig.init(
@@ -196,7 +197,7 @@ class RequiredUnlessDev(tuple, object):
     @staticmethod
     def __new__(cls, *args):
         assert len(args) == 3, "{}: len(args)!=3".format(args)
-        if channel_in("dev"):
+        if in_dev_mode():
             return args
         return Required(args[1], args[2])
 
@@ -278,6 +279,16 @@ def init(**kwargs):
     return res
 
 
+def in_dev_mode():
+    """Turn on developer features
+
+    Returns:
+        bool: value of `pykern.pykern.cfg.dev_mode`
+
+    """
+    return cfg.dev_mode
+
+
 def flatten_values(base, new):
     """Merge flattened values into base
 
@@ -668,10 +679,13 @@ def _coalesce_values():
     values[CHANNEL_ATTR] = channel
     _raw_values = values
     _parsed_values = dict(((_Key([k]), v) for k, v in env.items()))
-    cfg = init(
+    a = PKDict(
         _caller_module=sys.modules[__name__],
         channel=Required(str, "which (stage) function returns config"),
     )
+    cfg = init(**a)
+    a.dev_mode = (channel_in("dev"), bool, "controls development features")
+    cfg = init(**a)
     return _raw_values
 
 


Test patch:
diff --git a/tests/pkconfig4_test.py b/tests/pkconfig4_test.py
index 7078b95..290781c 100644
--- a/tests/pkconfig4_test.py
+++ b/tests/pkconfig4_test.py
@@ -12,12 +12,14 @@ def test_init(pkconfig_setup):
     """Validate parse_set"""
     pkconfig = pkconfig_setup(
         cfg=dict(P1_M1_SET3="", P1_M1_SET4="a:b"),
-        env=dict(P1_M1_REQ8="99"),
+        env=dict(P1_M1_REQ8="99", PYKERN_PKCONFIG_CHANNEL="dev"),
     )
     from p1.m1 import cfg
+    from pykern import pkunit
 
-    assert set() == cfg.set1, "When set1 is none, is empty"
-    assert set([1]) == cfg.set2, "When set2 is none, is (1,)"
+    pkunit.pkeq(set(), cfg.set1)
+    pkunit.pkeq(set([1]), cfg.set2)
     pkconfig.reset_state_for_testing()
-    assert set() == cfg.set3, "set3 should be overriden to be empty"
-    assert set(("a", "b")) == cfg.set4, 'set4 should be overriden to be ("a", "b")'
+    pkunit.pkeq(set(), cfg.set3)
+    pkunit.pkeq(set(("a", "b")), cfg.set4)
+    pkunit.pkeq(True, pkconfig.in_dev_mode())
diff --git a/tests/pkconfig5_test.py b/tests/pkconfig5_test.py
index ec32bea..883b954 100644
--- a/tests/pkconfig5_test.py
+++ b/tests/pkconfig5_test.py
@@ -11,7 +11,7 @@ import pytest
 def test_to_environ(pkconfig_setup):
     pkconfig = pkconfig_setup(
         cfg=dict(OTHER_THING="", P1_M1_SET4="a:b"),
-        env=dict(P1_M1_REQ8="33"),
+        env=dict(P1_M1_REQ8="33", PYKERN_PKCONFIG_DEV_MODE="0"),
     )
 
     from pykern.pkcollections import PKDict
@@ -54,3 +54,4 @@ def test_to_environ(pkconfig_setup):
         ),
         a,
     )
+    pkunit.pkeq(False, pkconfig.in_dev_mode())
diff --git a/tests/pkconfig6_test.py b/tests/pkconfig6_test.py
index 8173d3f..917d8dd 100644
--- a/tests/pkconfig6_test.py
+++ b/tests/pkconfig6_test.py
@@ -4,7 +4,6 @@
 :copyright: Copyright (c) 2019 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
 
 
@@ -12,7 +11,8 @@ def test_init(pkconfig_setup):
     """Validate parse_set"""
     pkconfig = pkconfig_setup(
         cfg=dict(
-            PYKERN_PKCONFIG_CHANNEL="alpha",
+            PYKERN_PKCONFIG_CHANNEL="dev",
+            PYKERN_PKCONFIG_DEV_MODE="false",
             P1_M1_SET3="",
             P1_M1_SET4="a:b",
         ),
diff --git a/tests/pkconfig7_test.py b/tests/pkconfig7_test.py
index a26ff38..f9a56c7 100644
--- a/tests/pkconfig7_test.py
+++ b/tests/pkconfig7_test.py
@@ -4,7 +4,6 @@
 :copyright: Copyright (c) 2019 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
 
 
@@ -21,4 +20,5 @@ def test_init(pkconfig_setup):
     from pykern import pkunit
     from p1.m1 import cfg
 
-    assert cfg.req10 == ("h1", "h2")
+    pkunit.pkeq(("h1", "h2"), cfg.req10)
+    pkunit.pkeq(False, pkconfig.in_dev_mode())