Skip to content

Commit 87e7e75

Browse files
committed
add robust lint/testing + upgrade idom
1 parent 0c57513 commit 87e7e75

19 files changed

+334
-57
lines changed

.github/ISSUE_TEMPLATE/bug_report.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
---
2+
name: Bug report
3+
about: Create a report to help us improve
4+
title: Bug Report
5+
labels: bug
6+
assignees: rmorshea
7+
8+
---
9+
10+
**Describe the bug**
11+
A clear and concise description of what the bug is.
12+
13+
**To Reproduce**
14+
Steps to reproduce the behavior:
15+
1. Go to '...'
16+
2. Click on '....'
17+
3. Scroll down to '....'
18+
4. See error
19+
20+
**Expected behavior**
21+
A clear and concise description of what you expected to happen.
22+
23+
**Additional context**
24+
Add any other context about the problem here.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
name: Doc enhancement
3+
about: Documentation needs to be fixed or added
4+
title: Doc Enhancement
5+
labels: docs
6+
assignees: rmorshea
7+
8+
---
9+
10+
**Describe what documentation needs to be fixed or added**
11+
Is something missing, worded poorly, or flat out wrong? Tells us about it here.
12+
13+
**Additional context**
14+
Add any other context about the problem here.
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
---
2+
name: Feature request
3+
about: Suggest an idea for this project
4+
title: ''
5+
labels: enhancement
6+
assignees: rmorshea
7+
8+
---
9+
10+
**Is your feature request related to a problem? Please describe.**
11+
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12+
13+
**Describe the solution you'd like**
14+
A clear and concise description of what you want to happen.
15+
16+
**Describe alternatives you've considered**
17+
A clear and concise description of any alternative solutions or features you've considered.
18+
19+
**Additional context**
20+
Add any other context or screenshots about the feature request here.

.github/workflows/release.yaml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# This workflows will upload a Python Package using Twine when a release is created
2+
# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries
3+
4+
name: Release
5+
6+
on:
7+
release:
8+
types:
9+
- created
10+
11+
jobs:
12+
publish-package:
13+
runs-on: ubuntu-latest
14+
steps:
15+
- uses: actions/checkout@v2
16+
- name: Set up Python
17+
uses: actions/setup-python@v1
18+
with:
19+
python-version: "3.x"
20+
- name: Install dependencies
21+
run: |
22+
python -m pip install --upgrade pip
23+
pip install setuptools wheel twine
24+
- name: Build and publish
25+
env:
26+
TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
27+
TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
28+
run: |
29+
python setup.py bdist_wheel
30+
twine upload dist/*

.github/workflows/test.yaml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
name: Test
2+
3+
on: [push]
4+
5+
jobs:
6+
coverage:
7+
runs-on: ubuntu-latest
8+
steps:
9+
- uses: actions/checkout@v2
10+
- name: Use Latest Python
11+
uses: actions/setup-python@v2
12+
with:
13+
python-version: "3.10"
14+
- name: Install Python Dependencies
15+
run: pip install -r requirements/nox-deps.txt
16+
- name: Run Tests
17+
run: nox -s test
18+
19+
environments:
20+
runs-on: ubuntu-latest
21+
strategy:
22+
matrix:
23+
python-version: ["3.7", "3.8", "3.9", "3.10"]
24+
steps:
25+
- uses: actions/checkout@v2
26+
- name: Use Python ${{ matrix.python-version }}
27+
uses: actions/setup-python@v2
28+
with:
29+
python-version: ${{ matrix.python-version }}
30+
- name: Install Python Dependencies
31+
run: pip install -r requirements/nox-deps.txt
32+
- name: Run Tests
33+
run: nox -s test -- --no-cov

idom_router/__init__.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,12 @@
11
# the version is statically loaded by setup.py
22
__version__ = "0.0.1"
3+
4+
from .router import Link, Route, Routes, configure, use_location
5+
6+
__all__ = [
7+
"configure",
8+
"Link",
9+
"Route",
10+
"Routes",
11+
"use_location",
12+
]

idom_router/router.py

Lines changed: 58 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,85 @@
11
from __future__ import annotations
2-
from dataclasses import dataclass
32

3+
import re
4+
from dataclasses import dataclass
45
from fnmatch import translate as fnmatch_translate
56
from pathlib import Path
6-
import re
7-
from typing import Any, Iterator, Protocol, Callable, Sequence
7+
from typing import Any, Callable, Iterator, Sequence
88

9-
from idom import create_context, component, use_context, use_state
10-
from idom.web.module import export, module_from_file
11-
from idom.core.vdom import coalesce_attributes_and_children, VdomAttributesAndChildren
9+
from idom import component, create_context, use_context, use_state
10+
from idom.core.types import VdomAttributesAndChildren, VdomDict
11+
from idom.core.vdom import coalesce_attributes_and_children
1212
from idom.types import BackendImplementation, ComponentType, Context, Location
13+
from idom.web.module import export, module_from_file
1314

15+
try:
16+
from typing import Protocol
17+
except ImportError:
18+
from typing_extensions import Protocol
1419

15-
class Router(Protocol):
20+
21+
class Routes(Protocol):
1622
def __call__(self, *routes: Route) -> ComponentType:
1723
...
1824

1925

20-
def bind(backend: BackendImplementation) -> Router:
26+
def configure(
27+
implementation: BackendImplementation[Any] | Callable[[], Location]
28+
) -> Routes:
29+
if isinstance(implementation, BackendImplementation):
30+
use_location = implementation.use_location
31+
elif callable(implementation):
32+
use_location = implementation
33+
else:
34+
raise TypeError(
35+
"Expected a BackendImplementation or "
36+
f"`use_location` hook, not {implementation}"
37+
)
38+
2139
@component
22-
def Router(*routes: Route):
23-
initial_location = backend.use_location()
40+
def Router(*routes: Route) -> ComponentType | None:
41+
initial_location = use_location()
2442
location, set_location = use_state(initial_location)
2543
for p, r in _compile_routes(routes):
26-
if p.match(location.pathname):
44+
match = p.match(location.pathname)
45+
if match:
2746
return _LocationStateContext(
2847
r.element,
29-
value=(location, set_location),
30-
key=r.path,
48+
value=_LocationState(location, set_location, match),
49+
key=p.pattern,
3150
)
3251
return None
3352

3453
return Router
3554

3655

37-
def use_location() -> str:
38-
return _use_location_state()[0]
56+
def use_location() -> Location:
57+
return _use_location_state().location
58+
59+
60+
def use_match() -> re.Match[str]:
61+
return _use_location_state().match
3962

4063

4164
@dataclass
4265
class Route:
43-
path: str | re.Pattern
66+
path: str | re.Pattern[str]
4467
element: Any
4568

4669

4770
@component
48-
def Link(*attributes_or_children: VdomAttributesAndChildren, to: str) -> None:
71+
def Link(*attributes_or_children: VdomAttributesAndChildren, to: str) -> VdomDict:
4972
attributes, children = coalesce_attributes_and_children(attributes_or_children)
50-
set_location = _use_location_state()[1]
51-
return _Link(
52-
{
53-
**attributes,
54-
"to": to,
55-
"onClick": lambda event: set_location(Location(**event)),
56-
},
57-
*children,
58-
)
59-
60-
61-
def _compile_routes(routes: Sequence[Route]) -> Iterator[tuple[re.Pattern, Route]]:
73+
set_location = _use_location_state().set_location
74+
attrs = {
75+
**attributes,
76+
"to": to,
77+
"onClick": lambda event: set_location(Location(**event)),
78+
}
79+
return _Link(attrs, *children)
80+
81+
82+
def _compile_routes(routes: Sequence[Route]) -> Iterator[tuple[re.Pattern[str], Route]]:
6283
for r in routes:
6384
if isinstance(r.path, re.Pattern):
6485
yield r.path, r
@@ -75,9 +96,14 @@ def _use_location_state() -> _LocationState:
7596
return location_state
7697

7798

78-
_LocationSetter = Callable[[str], None]
79-
_LocationState = tuple[Location, _LocationSetter]
80-
_LocationStateContext: type[Context[_LocationState | None]] = create_context(None)
99+
@dataclass
100+
class _LocationState:
101+
location: Location
102+
set_location: Callable[[Location], None]
103+
match: re.Match[str]
104+
105+
106+
_LocationStateContext: Context[_LocationState | None] = create_context(None)
81107

82108
_Link = export(
83109
module_from_file(

noxfile.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
from pathlib import Path
2+
3+
from nox import Session, session
4+
5+
ROOT = Path(".")
6+
REQUIREMENTS_DIR = ROOT / "requirements"
7+
8+
9+
@session
10+
def format(session: Session) -> None:
11+
install_requirements(session, "style")
12+
session.run("black", ".")
13+
session.run("isort", ".")
14+
15+
16+
@session
17+
def test(session: Session) -> None:
18+
session.notify("test_style")
19+
session.notify("test_types")
20+
session.notify("test_suite")
21+
22+
23+
@session
24+
def test_style(session: Session) -> None:
25+
install_requirements(session, "check-style")
26+
session.run("black", "--check", ".")
27+
session.run("isort", "--check", ".")
28+
session.run("flake8", ".")
29+
30+
31+
@session
32+
def test_types(session: Session) -> None:
33+
install_requirements(session, "check-types")
34+
session.run("mypy", "--strict", "idom_router")
35+
36+
37+
@session
38+
def test_suite(session: Session) -> None:
39+
install_requirements(session, "test-env")
40+
session.run("playwright", "install", "chromium")
41+
42+
posargs = session.posargs[:]
43+
44+
if "--no-cov" in session.posargs:
45+
posargs.remove("--no-cov")
46+
session.log("Coverage won't be checked")
47+
session.install(".")
48+
else:
49+
posargs += ["--cov=idom_router", "--cov-report=term"]
50+
session.install("-e", ".")
51+
52+
session.run("pytest", "tests", *posargs)
53+
54+
55+
def install_requirements(session: Session, name: str) -> None:
56+
session.install("-r", str(REQUIREMENTS_DIR / f"{name}.txt"))

pyproject.toml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,14 @@ build-backend = "setuptools.build_meta"
66
[tool.pytest.ini_options]
77
testpaths = "tests"
88
asyncio_mode = "auto"
9+
10+
11+
[tool.isort]
12+
profile = "black"
13+
14+
15+
[tool.mypy]
16+
ignore_missing_imports = true
17+
warn_unused_configs = true
18+
warn_redundant_casts = true
19+
warn_unused_ignores = true

requirements.txt

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
twine
2-
pytest
3-
pytest-asyncio
4-
idom[testing,starlette]
1+
-r requirements/check-style.txt
2+
-r requirements/check-types.txt
3+
-r requirements/nox-deps.txt
4+
-r requirements/pkg-deps.txt
5+
-r requirements/test-env.txt

requirements/check-style.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
black
2+
flake8
3+
flake8_idom_hooks
4+
isort

requirements/check-types.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
mypy
2+
idom

requirements/nox-deps.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
nox

requirements/pkg-deps.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
idom >=0.40.2,<0.41
2+
typing_extensions

requirements/test-env.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
twine
2+
pytest
3+
pytest-asyncio
4+
pytest-cov
5+
idom[testing,starlette]

setup.cfg

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,21 @@
11
[bdist_wheel]
22
universal=1
3+
4+
[flake8]
5+
ignore = E203, E266, E501, W503, F811, N802
6+
max-line-length = 88
7+
extend-exclude =
8+
.nox
9+
venv
10+
.venv
11+
tests/cases/*
12+
13+
[coverage:report]
14+
fail_under = 100
15+
show_missing = True
16+
skip_covered = True
17+
sort = Miss
18+
exclude_lines =
19+
pragma: no cover
20+
\.\.\.
21+
raise NotImplementedError

0 commit comments

Comments
 (0)