diff --git a/.github/workflows/python-checks.yml b/.github/workflows/python-checks.yml new file mode 100644 index 000000000..4915cfba5 --- /dev/null +++ b/.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 diff --git a/pynecone/components/layout/foreach.py b/pynecone/components/layout/foreach.py index a89c67d93..5bde63cc2 100644 --- a/pynecone/components/layout/foreach.py +++ b/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: diff --git a/pynecone/components/typography/markdown.py b/pynecone/components/typography/markdown.py index dbdf7582a..7df251533 100644 --- a/pynecone/components/typography/markdown.py +++ b/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'" diff --git a/pynecone/config.py b/pynecone/config.py index 32f4b0163..922d6bc15 100644 --- a/pynecone/config.py +++ b/pynecone/config.py @@ -1,3 +1,5 @@ +"""The Pynecone config.""" + from typing import Optional from pynecone import constants diff --git a/pynecone/state.py b/pynecone/state.py index 6418003d6..35aa6f559 100644 --- a/pynecone/state.py +++ b/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() diff --git a/pynecone/utils.py b/pynecone/utils.py index 704e764ac..a0b4f00a9 100644 --- a/pynecone/utils.py +++ b/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 diff --git a/tests/components/test_tag.py b/tests/components/test_tag.py index 9d8fda012..d2b96a21f 100644 --- a/tests/components/test_tag.py +++ b/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 diff --git a/tests/test_app.py b/tests/test_app.py index fa299670d..6acfee94d 100644 --- a/tests/test_app.py +++ b/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) diff --git a/tests/test_event.py b/tests/test_event.py index 582ebfad9..0f56382b6 100644 --- a/tests/test_event.py +++ b/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 == ()