瀏覽代碼

Create Github Action for pytest (#12)

Nikhil Rao 2 年之前
父節點
當前提交
c4b1f2c669

+ 39 - 0
.github/workflows/python-checks.yml

@@ -0,0 +1,39 @@
+name: Python application
+
+on:
+  push:
+    branches: [ "main" ]
+  pull_request:
+    branches: [ "main" ]
+
+permissions:
+  contents: read
+
+jobs:
+  build:
+
+    runs-on: ubuntu-latest
+    strategy:
+      matrix:
+        python-version: ["3.7", "3.8", "3.9", "3.10"]
+
+    steps:
+    - uses: actions/checkout@v3
+
+    - name: Set up Python ${{ matrix.python-version }}
+      uses: actions/setup-python@v4
+      with:
+        python-version: ${{ matrix.python-version }}
+
+    - uses: snok/install-poetry@v1
+      with:
+        version: 1.1.14
+        virtualenvs-create: true
+        virtualenvs-in-project: true
+
+    - run: poetry install --no-interaction --no-root
+    - run: poetry install --no-interaction
+    - run: poetry run pytest tests
+    - run: poetry run pyright pynecone tests
+    - run: poetry run pydocstyle pynecone tests
+    - run: poetry run darglint pynecone tests

+ 3 - 20
pynecone/components/layout/foreach.py

@@ -1,30 +1,13 @@
 """Create a list of components from an iterable."""
 from __future__ import annotations
 
-from typing import Any, List, Protocol, runtime_checkable
+from typing import Any, Callable, List
 
 from pynecone.components.component import Component
 from pynecone.components.tags import IterTag, Tag
 from pynecone.var import BaseVar, Var
 
 
-@runtime_checkable
-class RenderFn(Protocol):
-    """A function that renders a component."""
-
-    def __call__(self, *args, **kwargs) -> Component:
-        """Render a component.
-
-        Args:
-            *args: The positional arguments.
-            **kwargs: The keyword arguments.
-
-        Returns: # noqa: DAR202
-            The rendered component.
-        """
-        ...
-
-
 class Foreach(Component):
     """Display a foreach."""
 
@@ -32,10 +15,10 @@ class Foreach(Component):
     iterable: Var[List]
 
     # A function from the render args to the component.
-    render_fn: RenderFn
+    render_fn: Callable
 
     @classmethod
-    def create(cls, iterable: Var[List], render_fn: RenderFn, **props) -> Foreach:
+    def create(cls, iterable: Var[List], render_fn: Callable, **props) -> Foreach:
         """Create a foreach component.
 
         Args:

+ 1 - 1
pynecone/components/typography/markdown.py

@@ -14,7 +14,7 @@ class Markdown(Component):
 
     tag = "ReactMarkdown"
 
-    src: Var[str] = ""  # type: ignore
+    src: Var[str]
 
     def _get_custom_code(self) -> str:
         return "import 'katex/dist/katex.min.css'"

+ 2 - 0
pynecone/config.py

@@ -1,3 +1,5 @@
+"""The Pynecone config."""
+
 from typing import Optional
 
 from pynecone import constants

+ 5 - 1
pynecone/state.py

@@ -467,7 +467,11 @@ class StateManager(Base):
     redis: Any = None
 
     def setup(self, state: Type[State]):
-        """Setup the state manager."""
+        """Set up the state manager.
+
+        Args:
+            state: The state class to use.
+        """
         self.state = state
         self.redis = utils.get_redis()
 

+ 21 - 10
pynecone/utils.py

@@ -15,7 +15,6 @@ import sys
 from collections import defaultdict
 from subprocess import PIPE
 from typing import _GenericAlias  # type: ignore
-from typing import _UnionGenericAlias  # type: ignore
 from typing import (
     TYPE_CHECKING,
     Any,
@@ -71,7 +70,7 @@ def get_base_class(cls: Type) -> Type:
     """
     # For newer versions of Python.
     try:
-        from types import GenericAlias
+        from types import GenericAlias  # type: ignore
 
         if isinstance(cls, GenericAlias):
             return get_base_class(cls.__origin__)
@@ -79,11 +78,18 @@ def get_base_class(cls: Type) -> Type:
         pass
 
     # Check Union types first.
-    if isinstance(cls, _UnionGenericAlias):
-        return tuple(get_base_class(arg) for arg in get_args(cls))
+    try:
+        from typing import _UnionGenericAlias  # type: ignore
+
+        if isinstance(cls, _UnionGenericAlias):
+            return tuple(get_base_class(arg) for arg in get_args(cls))
+    except:
+        pass
 
     # Check other generic aliases.
     if isinstance(cls, _GenericAlias):
+        if cls.__origin__ == Union:
+            return tuple(get_base_class(arg) for arg in get_args(cls))
         return get_base_class(cls.__origin__)
 
     # This is the base class.
@@ -105,7 +111,7 @@ def _issubclass(
     # Special check for Any.
     if cls_check == Any:
         return True
-    if cls == Any:
+    if cls == Any or cls == Callable:
         return False
     cls_base = get_base_class(cls)
     cls_check_base = get_base_class(cls_check)
@@ -240,8 +246,13 @@ def get_config() -> Config:
     Returns:
         The app config.
     """
+    from pynecone.config import Config
+
     sys.path.append(os.getcwd())
-    return __import__(constants.CONFIG_MODULE).config
+    try:
+        return __import__(constants.CONFIG_MODULE).config
+    except:
+        return Config(app_name="")
 
 
 def get_bun_path():
@@ -694,9 +705,9 @@ def format_string(string: str) -> str:
     Returns:
         The formatted string.
     """
-    # Escale backticks.
-    string = string.replace("\`", "`")  # type: ignore
-    string = string.replace("`", "\`")  # type: ignore
+    # Escape backticks.
+    string = string.replace(r"\`", "`")
+    string = string.replace("`", r"\`")
 
     # Wrap the string so it looks like {`string`}.
     string = wrap(string, "`")
@@ -859,7 +870,7 @@ def get_redis():
         The redis client.
     """
     try:
-        import redis
+        import redis  # type: ignore
     except:
         return None
 

+ 1 - 1
tests/components/test_tag.py

@@ -4,7 +4,7 @@ import pytest
 
 from pynecone.components import Box
 from pynecone.components.tags import CondTag, IterTag, Tag
-from pynecone.event import EventHandler, EventSpec, EventChain
+from pynecone.event import EventChain, EventHandler, EventSpec
 from pynecone.var import BaseVar, Var
 
 

+ 13 - 5
tests/test_app.py

@@ -3,8 +3,8 @@ from typing import Type
 import pytest
 
 from pynecone.app import App, DefaultState
-from pynecone.middleware import HydrateMiddleware
 from pynecone.components import Box
+from pynecone.middleware import HydrateMiddleware
 from pynecone.state import State
 from pynecone.style import Style
 
@@ -21,7 +21,11 @@ def app() -> App:
 
 @pytest.fixture
 def index_page():
-    """An index page."""
+    """An index page.
+
+    Returns:
+        The index page.
+    """
 
     def index():
         return Box.create("Index")
@@ -31,7 +35,11 @@ def index_page():
 
 @pytest.fixture
 def about_page():
-    """An index page."""
+    """An about page.
+
+    Returns:
+        The about page.
+    """
 
     def about():
         return Box.create("About")
@@ -107,7 +115,7 @@ def test_initialize_with_state(TestState: Type[State]):
     """Test setting the state of an app.
 
     Args:
-        DefaultState: The default state.
+        TestState: The default state.
     """
     app = App(state=TestState)
     assert app.state == TestState
@@ -123,7 +131,7 @@ def test_set_and_get_state(TestState: Type[State]):
     """Test setting and getting the state of an app with different tokens.
 
     Args:
-        DefaultState: The default state.
+        TestState: The default state.
     """
     app = App(state=TestState)
 

+ 16 - 1
tests/test_event.py

@@ -1,6 +1,21 @@
 import pytest
 
 from pynecone.event import Event, EventHandler, EventSpec
+from pynecone.var import Var
+
+
+def make_var(value) -> Var:
+    """Make a variable.
+
+    Args:
+        value: The value of the var.
+
+    Returns:
+        The var.
+    """
+    var = Var.create(value)
+    assert var is not None
+    return var
 
 
 def test_create_event():
@@ -28,7 +43,7 @@ def test_call_event_handler():
     assert event_spec.args == ()
 
     handler = EventHandler(fn=test_fn_with_args)
-    event_spec = handler("first", "second")
+    event_spec = handler(make_var("first"), make_var("second"))
 
     assert event_spec.handler == handler
     assert event_spec.local_args == ()