Răsfoiți Sursa

Create Github Action for pytest (#12)

Nikhil Rao 2 ani în urmă
părinte
comite
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."""
 """Create a list of components from an iterable."""
 from __future__ import annotations
 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.component import Component
 from pynecone.components.tags import IterTag, Tag
 from pynecone.components.tags import IterTag, Tag
 from pynecone.var import BaseVar, Var
 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):
 class Foreach(Component):
     """Display a foreach."""
     """Display a foreach."""
 
 
@@ -32,10 +15,10 @@ class Foreach(Component):
     iterable: Var[List]
     iterable: Var[List]
 
 
     # A function from the render args to the component.
     # A function from the render args to the component.
-    render_fn: RenderFn
+    render_fn: Callable
 
 
     @classmethod
     @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.
         """Create a foreach component.
 
 
         Args:
         Args:

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

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

+ 2 - 0
pynecone/config.py

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

+ 5 - 1
pynecone/state.py

@@ -467,7 +467,11 @@ class StateManager(Base):
     redis: Any = None
     redis: Any = None
 
 
     def setup(self, state: Type[State]):
     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.state = state
         self.redis = utils.get_redis()
         self.redis = utils.get_redis()
 
 

+ 21 - 10
pynecone/utils.py

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

+ 1 - 1
tests/components/test_tag.py

@@ -4,7 +4,7 @@ import pytest
 
 
 from pynecone.components import Box
 from pynecone.components import Box
 from pynecone.components.tags import CondTag, IterTag, Tag
 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
 from pynecone.var import BaseVar, Var
 
 
 
 

+ 13 - 5
tests/test_app.py

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

+ 16 - 1
tests/test_event.py

@@ -1,6 +1,21 @@
 import pytest
 import pytest
 
 
 from pynecone.event import Event, EventHandler, EventSpec
 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():
 def test_create_event():
@@ -28,7 +43,7 @@ def test_call_event_handler():
     assert event_spec.args == ()
     assert event_spec.args == ()
 
 
     handler = EventHandler(fn=test_fn_with_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.handler == handler
     assert event_spec.local_args == ()
     assert event_spec.local_args == ()