Skip to content

Commit ee6f237

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

19 files changed

+327
-57
lines changed

Diff for: .github/ISSUE_TEMPLATE/bug_report.md

+24
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.

Diff for: .github/ISSUE_TEMPLATE/doc_enhancement.md

+14
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.

Diff for: .github/ISSUE_TEMPLATE/feature_request.md

+20
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.

Diff for: .github/workflows/release.yaml

+30
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/*

Diff for: .github/workflows/test.yaml

+33
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

Diff for: idom_router/__init__.py

+10
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+
]

Diff for: idom_router/router.py

+53-32
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,80 @@
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, Protocol, 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

1415

15-
class Router(Protocol):
16+
class Routes(Protocol):
1617
def __call__(self, *routes: Route) -> ComponentType:
1718
...
1819

1920

20-
def bind(backend: BackendImplementation) -> Router:
21+
def configure(
22+
implementation: BackendImplementation[Any] | Callable[[], Location]
23+
) -> Routes:
24+
if isinstance(implementation, BackendImplementation):
25+
use_location = implementation.use_location
26+
elif callable(implementation):
27+
use_location = implementation
28+
else:
29+
raise TypeError(
30+
"Expected a BackendImplementation or "
31+
f"`use_location` hook, not {implementation}"
32+
)
33+
2134
@component
22-
def Router(*routes: Route):
23-
initial_location = backend.use_location()
35+
def Router(*routes: Route) -> ComponentType | None:
36+
initial_location = use_location()
2437
location, set_location = use_state(initial_location)
2538
for p, r in _compile_routes(routes):
26-
if p.match(location.pathname):
39+
match = p.match(location.pathname)
40+
if match:
2741
return _LocationStateContext(
2842
r.element,
29-
value=(location, set_location),
30-
key=r.path,
43+
value=_LocationState(location, set_location, match),
44+
key=p.pattern,
3145
)
3246
return None
3347

3448
return Router
3549

3650

37-
def use_location() -> str:
38-
return _use_location_state()[0]
51+
def use_location() -> Location:
52+
return _use_location_state().location
53+
54+
55+
def use_match() -> re.Match[str]:
56+
return _use_location_state().match
3957

4058

4159
@dataclass
4260
class Route:
43-
path: str | re.Pattern
61+
path: str | re.Pattern[str]
4462
element: Any
4563

4664

4765
@component
48-
def Link(*attributes_or_children: VdomAttributesAndChildren, to: str) -> None:
66+
def Link(*attributes_or_children: VdomAttributesAndChildren, to: str) -> VdomDict:
4967
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]]:
68+
set_location = _use_location_state().set_location
69+
attrs = {
70+
**attributes,
71+
"to": to,
72+
"onClick": lambda event: set_location(Location(**event)),
73+
}
74+
return _Link(attrs, *children)
75+
76+
77+
def _compile_routes(routes: Sequence[Route]) -> Iterator[tuple[re.Pattern[str], Route]]:
6278
for r in routes:
6379
if isinstance(r.path, re.Pattern):
6480
yield r.path, r
@@ -75,9 +91,14 @@ def _use_location_state() -> _LocationState:
7591
return location_state
7692

7793

78-
_LocationSetter = Callable[[str], None]
79-
_LocationState = tuple[Location, _LocationSetter]
80-
_LocationStateContext: type[Context[_LocationState | None]] = create_context(None)
94+
@dataclass
95+
class _LocationState:
96+
location: Location
97+
set_location: Callable[[Location], None]
98+
match: re.Match[str]
99+
100+
101+
_LocationStateContext: Context[_LocationState | None] = create_context(None)
81102

82103
_Link = export(
83104
module_from_file(

Diff for: noxfile.py

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
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+
41+
posargs = session.posargs[:]
42+
43+
if "--no-cov" in session.posargs:
44+
posargs.remove("--no-cov")
45+
session.log("Coverage won't be checked")
46+
session.install(".")
47+
else:
48+
posargs += ["--cov=idom_router", "--cov-report=term"]
49+
session.install("-e", ".")
50+
51+
session.run("pytest", "tests", *posargs)
52+
53+
54+
def install_requirements(session: Session, name: str) -> None:
55+
session.install("-r", str(REQUIREMENTS_DIR / f"{name}.txt"))

Diff for: pyproject.toml

+11
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

Diff for: requirements.txt

+5-4
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

Diff for: requirements/check-style.txt

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
black
2+
flake8
3+
flake8_idom_hooks
4+
isort

Diff for: requirements/check-types.txt

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
mypy
2+
idom

Diff for: requirements/nox-deps.txt

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
nox

Diff for: requirements/pkg-deps.txt

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
idom >=0.40.2,<0.41

Diff for: requirements/test-env.txt

+5
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]

Diff for: setup.cfg

+19
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)