From 2a389020826f20b35195c148aa0270d892ef870d Mon Sep 17 00:00:00 2001 From: b3b Date: Fri, 11 Dec 2020 15:48:18 +0300 Subject: [PATCH] add `%there kv` command --- pythonhere/__init__.py | 7 +++++++ pythonhere/magic_here/__init__.py | 0 pythonhere/magic_here/shortcuts.py | 29 +++++++++++++++++++++++++++++ run_tests.sh | 2 +- setup.py | 4 ++++ tests/conftest.py | 22 ++++++++++++++++++++++ tests/test_magic.py | 21 +++++++++++++++++++++ tox.ini | 1 + 8 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 pythonhere/__init__.py create mode 100644 pythonhere/magic_here/__init__.py create mode 100644 pythonhere/magic_here/shortcuts.py create mode 100644 tests/test_magic.py diff --git a/pythonhere/__init__.py b/pythonhere/__init__.py new file mode 100644 index 0000000..8ba7d97 --- /dev/null +++ b/pythonhere/__init__.py @@ -0,0 +1,7 @@ +"""Python Here Jupyter magic.""" +from herethere.magic import load_ipython_extension + +from .magic_here import shortcuts # noqa + + +__all__ = ("load_ipython_extension",) diff --git a/pythonhere/magic_here/__init__.py b/pythonhere/magic_here/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pythonhere/magic_here/shortcuts.py b/pythonhere/magic_here/shortcuts.py new file mode 100644 index 0000000..e94497d --- /dev/null +++ b/pythonhere/magic_here/shortcuts.py @@ -0,0 +1,29 @@ +"""%there magic Python code shortcuts.""" +# pylint: disable=invalid-name + +import click +from herethere.there.commands import there_code_shortcut + +KV_COMMAND_TEMPLATE = r""" +from kivy.lang import Builder +{unload_file} +_pythonhere_new_root = Builder.load_string(r'''{code} ''', filename='_pythonhere') +if _pythonhere_new_root: + root.clear_widgets() + root.add_widget(_pythonhere_new_root) +del _pythonhere_new_root +""" + + +@there_code_shortcut +@click.option( + "-c", "--clear-style", is_flag=True, help="Unload previously applied rules." +) +def kv(code: str, clear_style: bool) -> str: + """Insert given rules into the Kivy Language Builder. + + :param code: KV language rules + """ + unload_file = "Builder.unload_file('_pythonhere')" if clear_style else "" + code = code.replace("'''", '"""') + return KV_COMMAND_TEMPLATE.format(code=code, unload_file=unload_file) diff --git a/run_tests.sh b/run_tests.sh index 8962cf2..4284a42 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -4,4 +4,4 @@ set -e cd pythonhere PYTHONPATH=. xvfb-run --auto-servernum pytest --cov=. --cov-config=../.coveragerc --cov-report=xml ../tests -coverage report +coverage report -i diff --git a/setup.py b/setup.py index d2a3dee..5f58915 100644 --- a/setup.py +++ b/setup.py @@ -26,6 +26,10 @@ "ifaddr", ], extras_require={ + "magic": [ + "ipython", + "ipywidgets", + ], "dev": [ "black", "codecov", diff --git a/tests/conftest.py b/tests/conftest.py index 6c98354..266493a 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,12 +1,16 @@ import asyncio + +import nest_asyncio import pytest from herethere.everywhere import ConnectionConfig from herethere.there.client import Client +from herethere.there.commands import ContextObject, there_group from main import PythonHereApp, run_ssh_server + @pytest.fixture def connection_config(): return ConnectionConfig( @@ -35,3 +39,21 @@ async def there(app_instance, connection_config): await asyncio.wait_for(app_instance.ssh_server_started.wait(), 5) await client.connect(connection_config) yield client + + +@pytest.fixture +def nested_event_loop(event_loop): + nest_asyncio.apply() + + +@pytest.fixture +async def call_there_group(nested_event_loop, app_instance, there): + def _callable(args, code): + there_group( + args, + "test", + standalone_mode=False, + obj=ContextObject(client=there, code=code), + ) + + return _callable diff --git a/tests/test_magic.py b/tests/test_magic.py new file mode 100644 index 0000000..1fce966 --- /dev/null +++ b/tests/test_magic.py @@ -0,0 +1,21 @@ +import pytest +from herethere.there.commands import ContextObject + +from magic_here import shortcuts # noqa + +@pytest.mark.asyncio +async def test_kv_command_runcode_called(call_there_group, mocker): + runcode = mocker.patch.object(ContextObject, 'runcode', autospec=True) + + call_there_group(["kv"], "Label:") + + runcode.assert_called_once() + ctx_obj = runcode.call_args[0][0] + assert "Builder.load_string(r'''Label: '''" in ctx_obj.code + +@pytest.mark.asyncio +async def test_kv_command_executed(capfd, app_instance, call_there_group): + call_there_group(["kv"], "Label:\n text: '''Hello there'''") + captured = capfd.readouterr() + assert not captured.out and not captured.err + assert app_instance.root.children[0].text == 'Hello there' diff --git a/tox.ini b/tox.ini index 7fe83d6..fb376ca 100644 --- a/tox.ini +++ b/tox.ini @@ -3,6 +3,7 @@ envlist = py37,py38 [testenv] extras = + magic dev commands = ./run_tests.sh