From 052ef488cefe2cf0534ebd4a4a949fdd5833eb60 Mon Sep 17 00:00:00 2001 From: Bryan Culver Date: Thu, 31 Oct 2019 02:18:34 -0400 Subject: [PATCH] Refactor CLI Folder Structure (#12) (#196) * Refactor CLI Folder Structure (#12) - Creates separate command folder structure - Refactored cli __init__ to import commands explicitly * Rename 'cmd_server' to 'server' in cli commands. * Add simple tests for CLI * Change mokeypatching to mocking. Add pytest-mock. --- RELEASE.md | 3 ++ poetry.lock | 14 +++++++++- pyproject.toml | 1 + strawberry/cli/__init__.py | 41 ++------------------------- strawberry/cli/commands/__init__.py | 0 strawberry/cli/commands/server.py | 43 +++++++++++++++++++++++++++++ tests/cli/__init__.py | 0 tests/cli/helpers/sample_schema.py | 17 ++++++++++++ tests/cli/test_server.py | 32 +++++++++++++++++++++ 9 files changed, 111 insertions(+), 40 deletions(-) create mode 100644 RELEASE.md create mode 100644 strawberry/cli/commands/__init__.py create mode 100644 strawberry/cli/commands/server.py create mode 100644 tests/cli/__init__.py create mode 100644 tests/cli/helpers/sample_schema.py create mode 100644 tests/cli/test_server.py diff --git a/RELEASE.md b/RELEASE.md new file mode 100644 index 0000000000..35ffae0fef --- /dev/null +++ b/RELEASE.md @@ -0,0 +1,3 @@ +Release type: minor + +Refactored CLI folder structure, importing click commands from a subfolder. Follows click's complex example. \ No newline at end of file diff --git a/poetry.lock b/poetry.lock index e96df0513e..26fd6d600b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -490,6 +490,17 @@ version = "0.2.0" [package.dependencies] pytest = ">=4.2.1" +[[package]] +category = "dev" +description = "Thin-wrapper around the mock package for easier use with py.test" +name = "pytest-mock" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "1.11.2" + +[package.dependencies] +pytest = ">=2.7" + [[package]] category = "dev" description = "pytest plugin for writing tests for mypy plugins" @@ -671,7 +682,7 @@ more-itertools = "*" django = ["django"] [metadata] -content-hash = "3370d368db67e883024ad6cf6f083b4b66d3c011395a8048fea15a0c1e82accf" +content-hash = "693d40859eae90983b1eaa4ce1154065fe75e0361a82afcafa54acfd367bfead" python-versions = "^3.7" [metadata.hashes] @@ -724,6 +735,7 @@ pytest-asyncio = ["9fac5100fd716cbecf6ef89233e8590a4ad61d729d1732e0a96b84182df1d pytest-cov = ["cc6742d8bac45070217169f5f72ceee1e0e55b0221f54bcf24845972d3a47f2b", "cdbdef4f870408ebdbfeb44e63e07eb18bb4619fae852f6e760645fa36172626"] pytest-django = ["497e8d967d2ec82b3388267b2f1f037761ff34c10ebb13c534d8c5804846e4eb", "b6c900461a6a7c450dcf11736cabc289a90f5d6f28ef74c46e32e86ffd16a4bd"] pytest-emoji = ["6e34ed21970fa4b80a56ad11417456bd873eb066c02315fe9df0fafe6d4d4436", "e1bd4790d87649c2d09c272c88bdfc4d37c1cc7c7a46583087d7c510944571e8"] +pytest-mock = ["b3514caac35fe3f05555923eabd9546abce11571cc2ddf7d8615959d04f2c89e", "ea502c3891599c26243a3a847ccf0b1d20556678c528f86c98e3cd6d40c5cf11"] pytest-mypy-plugins = ["2aa73a7081dbb03dadfa3d2e7bc8d06544931799ef0d26d77c76519bcbaabe4b", "fc754203133ab528e66b0feb3466a4a2f6a5ac274e0d9c571d42c1e9c5d24aa4"] pytz = ["1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d", "b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be"] pyyaml = ["0113bc0ec2ad727182326b61326afa3d1d8280ae1122493553fd6f4397f33df9", "01adf0b6c6f61bd11af6e10ca52b7d4057dd0be0343eb9283c878cf3af56aee4", "5124373960b0b3f4aa7df1707e63e9f109b5263eca5976c66e08b1c552d4eaf8", "5ca4f10adbddae56d824b2c09668e91219bb178a1eee1faa56af6f99f11bf696", "7907be34ffa3c5a32b60b95f4d95ea25361c951383a894fec31be7252b2b6f34", "7ec9b2a4ed5cad025c2278a1e6a19c011c80a3caaac804fd2d329e9cc2c287c9", "87ae4c829bb25b9fe99cf71fbb2140c448f534e24c998cc60f39ae4f94396a73", "9de9919becc9cc2ff03637872a440195ac4241c80536632fffeb6a1e25a74299", "a5a85b10e450c66b49f98846937e8cfca1db3127a9d5d1e31ca45c3d0bef4c5b", "b0997827b4f6a7c286c01c5f60384d218dca4ed7d9efa945c3e1aa623d5709ae", "b631ef96d3222e62861443cc89d6563ba3eeb816eeb96b2629345ab795e53681", "bf47c0607522fdbca6c9e817a6e81b08491de50f3766a7a0e6a5be7905961b41", "f81025eddd0327c7d4cfe9b62cf33190e1e736cc6e97502b3ec425f574b3e7a8"] diff --git a/pyproject.toml b/pyproject.toml index 270f39f6db..85faad7065 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,6 +42,7 @@ mypy = "^0.740" flake8-bugbear = "^19.8" pytest-django = {version = "^3.6", optional = true} pytest-mypy-plugins = "^1.1" +pytest-mock = "^1.11" [tool.poetry.extras] django = ["django","pytest-django"] diff --git a/strawberry/cli/__init__.py b/strawberry/cli/__init__.py index be6cca2758..30306c6a0b 100644 --- a/strawberry/cli/__init__.py +++ b/strawberry/cli/__init__.py @@ -1,17 +1,6 @@ import click -import sys -import os -from starlette.applications import Starlette -from starlette.middleware.cors import CORSMiddleware - -import importlib - -import uvicorn - -import hupper - -from strawberry.asgi import GraphQL +from .commands.server import server as cmd_server @click.group() @@ -19,30 +8,4 @@ def run(): pass -@run.command("server") -@click.argument("module", type=str) -@click.option("-h", "--host", default="0.0.0.0", type=str) -@click.option("-p", "--port", default=8000, type=int) -def server(module, host, port): - sys.path.append(os.getcwd()) - - reloader = hupper.start_reloader("strawberry.cli.run", verbose=False) - - schema_module = importlib.import_module(module) - - reloader.watch_files([schema_module.__file__]) - - app = Starlette(debug=True) - - app.add_middleware( - CORSMiddleware, allow_headers=["*"], allow_origins=["*"], allow_methods=["*"] - ) - - graphql_app = GraphQL(schema_module.schema, debug=True) - - app.add_route("/graphql", graphql_app) - app.add_websocket_route("/graphql", graphql_app) - - print(f"Running strawberry on http://{host}:{port}/graphql 🍓") - - uvicorn.run(app, host=host, port=port, log_level="error") +run.add_command(cmd_server) diff --git a/strawberry/cli/commands/__init__.py b/strawberry/cli/commands/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/strawberry/cli/commands/server.py b/strawberry/cli/commands/server.py new file mode 100644 index 0000000000..0868fc47a2 --- /dev/null +++ b/strawberry/cli/commands/server.py @@ -0,0 +1,43 @@ +import click +import sys + +import os +from starlette.applications import Starlette +from starlette.middleware.cors import CORSMiddleware + +import importlib + +import uvicorn + +import hupper + +from strawberry.asgi import GraphQL + + +@click.command("server", short_help='Starts debug server') +@click.argument("module", type=str) +@click.option("-h", "--host", default="0.0.0.0", type=str) +@click.option("-p", "--port", default=8000, type=int) +def server(module, host, port): + sys.path.append(os.getcwd()) + + reloader = hupper.start_reloader("strawberry.cli.run", verbose=False) + + schema_module = importlib.import_module(module) + + reloader.watch_files([schema_module.__file__]) + + app = Starlette(debug=True) + + app.add_middleware( + CORSMiddleware, allow_headers=["*"], allow_origins=["*"], allow_methods=["*"] + ) + + graphql_app = GraphQL(schema_module.schema, debug=True) + + app.add_route("/graphql", graphql_app) + app.add_websocket_route("/graphql", graphql_app) + + print(f"Running strawberry on http://{host}:{port}/graphql 🍓") + + uvicorn.run(app, host=host, port=port, log_level="error") diff --git a/tests/cli/__init__.py b/tests/cli/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/cli/helpers/sample_schema.py b/tests/cli/helpers/sample_schema.py new file mode 100644 index 0000000000..1795c13486 --- /dev/null +++ b/tests/cli/helpers/sample_schema.py @@ -0,0 +1,17 @@ +import strawberry + + +@strawberry.type +class User: + name: str + age: int + + +@strawberry.type +class Query: + @strawberry.field + def user(self, info) -> User: + return User(name="Patrick", age=100) + + +schema = strawberry.Schema(query=Query) diff --git a/tests/cli/test_server.py b/tests/cli/test_server.py new file mode 100644 index 0000000000..c73a68a454 --- /dev/null +++ b/tests/cli/test_server.py @@ -0,0 +1,32 @@ +from click.testing import CliRunner + +from strawberry.cli.commands.server import server as cmd_server + +import uvicorn +import hupper + + +def test_cli_cmd_server(mocker): + + # Mock of uvicorn.run + uvicorn_run_patch = mocker.patch('uvicorn.run') + uvicorn_run_patch.return_value = True + # Mock to prevent the reloader from kicking in + hupper_reloader_patch = mocker.patch('hupper.start_reloader') + hupper_reloader_patch.return_value = MockReloader() + runner = CliRunner() + + result = runner.invoke(cmd_server, ['tests.cli.helpers.sample_schema'], "") + assert result.exit_code == 0 + + # We started the reloader + assert hupper.start_reloader.call_count == 1 + assert uvicorn.run.call_count == 1 + + assert result.output == 'Running strawberry on http://0.0.0.0:8000/graphql 🍓\n' + + +class MockReloader: + @staticmethod + def watch_files(*args, **kwargs): + return True