From 06888e70a567c1c28bd1ea8484d22f38d13a421e Mon Sep 17 00:00:00 2001 From: Mani Mozaffar Date: Wed, 1 May 2024 14:53:23 +0200 Subject: [PATCH] Fixed type issues and left detailed comments for unresolved issues --- demo/pyproject.toml | 2 +- demo/src/auth_user.py | 2 +- demo/src/components_list.py | 5 +- demo/src/forms.py | 2 +- demo/src/sse.py | 14 ++++-- demo/src/tables.py | 15 ++++-- src/python-fastui/fastui/json_schema.py | 4 +- src/python-fastui/tests/test_auth_github.py | 7 ++- src/python-fastui/tests/test_components.py | 4 +- src/python-fastui/tests/test_forms.py | 51 +++++++++++++++------ 10 files changed, 71 insertions(+), 35 deletions(-) diff --git a/demo/pyproject.toml b/demo/pyproject.toml index b5398d4f..1dbd854d 100644 --- a/demo/pyproject.toml +++ b/demo/pyproject.toml @@ -3,7 +3,7 @@ path = "src/__init__.py" allow-direct-references = true [project] -name = 'src' +name = 'demo' description = 'FastUI demo project' authors = [{ name = 'Samuel Colvin', email = 's@muelcolvin.com' }] classifiers = [ diff --git a/demo/src/auth_user.py b/demo/src/auth_user.py index 4bb53768..c711cc95 100644 --- a/demo/src/auth_user.py +++ b/demo/src/auth_user.py @@ -49,7 +49,7 @@ def from_request_opt(cls, authorization: Annotated[str, Header()] = '') -> Self class CustomJsonEncoder(json.JSONEncoder): - def default(self, obj: Any) -> Any: # type: ignore + def default(self, obj: Any) -> Any: if isinstance(obj, datetime): return obj.isoformat() else: diff --git a/demo/src/components_list.py b/demo/src/components_list.py index c34b9b84..74eefbe0 100644 --- a/demo/src/components_list.py +++ b/demo/src/components_list.py @@ -6,6 +6,7 @@ from fastui import AnyComponent, FastUI from fastui import components as c from fastui.events import GoToEvent, PageEvent +from pydantic_core import Url from .shared import demo_page @@ -222,7 +223,7 @@ class Delivery(BaseModel): components=[ c.Heading(text='Iframe', level=2), c.Markdown(text='`Iframe` can be used to embed external content.'), - c.Iframe(src='https://pydantic.dev', width='100%', height=400), # type: ignore + c.Iframe(src=Url('https://pydantic.dev'), width='100%', height=400), ], class_name='border-top mt-3 pt-1', ), @@ -260,7 +261,7 @@ class Delivery(BaseModel): c.Heading(text='Video', level=2), c.Paragraph(text='A video component.'), c.Video( - sources=['https://www.w3schools.com/html/mov_bbb.mp4'], # type: ignore + sources=[Url('https://www.w3schools.com/html/mov_bbb.mp4')], autoplay=False, controls=True, loop=False, diff --git a/demo/src/forms.py b/demo/src/forms.py index d23c80fd..692c5995 100644 --- a/demo/src/forms.py +++ b/demo/src/forms.py @@ -40,7 +40,7 @@ async def search_view(request: Request, q: str) -> SelectSearchResponse: for co in data: regions[co['region']].append({'value': co['cca3'], 'label': co['name']['common']}) options = [{'label': k, 'options': v} for k, v in regions.items()] - return SelectSearchResponse(options=options) # type: ignore + return SelectSearchResponse(options=options) # type: ignore (needs to be SelectOptions type) FormKind: TypeAlias = Literal['login', 'select', 'big'] diff --git a/demo/src/sse.py b/demo/src/sse.py index bd6c0d03..acd2cbb9 100644 --- a/demo/src/sse.py +++ b/demo/src/sse.py @@ -29,16 +29,19 @@ async def run_openai(): from time import perf_counter from openai import AsyncOpenAI + from openai.types.chat import ChatCompletionSystemMessageParam, ChatCompletionUserMessageParam messages = [ - {'role': 'system', 'content': 'please response in markdown only.'}, - {'role': 'user', 'content': 'What is SSE? Please include a javascript code example.'}, + ChatCompletionSystemMessageParam({'role': 'system', 'content': 'please response in markdown only.'}), + ChatCompletionUserMessageParam( + {'role': 'user', 'content': 'What is SSE? Please include a javascript code example.'} + ), ] chunks = await AsyncOpenAI().chat.completions.create( model='gpt-4', - messages=messages, # type: ignore + messages=messages, stream=True, - ) # type: ignore + ) last = None result_chunks = [] @@ -48,7 +51,8 @@ async def run_openai(): t = now - last else: t = 0 - text: str = chunk.choices[0].delta.content + text = chunk.choices[0].delta.content + assert text is None print(repr(text), t) if text is not None: result_chunks.append((t, text)) diff --git a/demo/src/tables.py b/demo/src/tables.py index 2726ab87..c7b5bfb7 100644 --- a/demo/src/tables.py +++ b/demo/src/tables.py @@ -76,7 +76,7 @@ def cities_view(page: int = 1, country: str | None = None) -> list[AnyComponent] DisplayLookup(field='population', table_width_percent=33), ], ), - c.Pagination(page=page, page_size=page_size, total=len(cities)), # type: ignore + c.Pagination(page=page, page_size=page_size, total=len(cities), page_query_param='page'), title='Cities', ) @@ -145,17 +145,24 @@ def tabs() -> list[AnyComponent]: @router.get('/users/{id}/', response_model=FastUI, response_model_exclude_none=True) def user_profile(id: int) -> list[AnyComponent]: - user: User | None = users[id - 1] if id <= len(users) else None + user = users[id - 1] if id <= len(users) else None + if user is None: + return demo_page( + *tabs(), + c.Text(text='User not found.'), + title='User not found', + ) + return demo_page( *tabs(), c.Link(components=[c.Text(text='Back')], on_click=BackEvent()), c.Details( - data=user, # type: ignore + data=user, fields=[ DisplayLookup(field='name'), DisplayLookup(field='dob', mode=DisplayMode.date), DisplayLookup(field='enabled'), ], ), - title=user.name, # type: ignore + title=user.name, ) diff --git a/src/python-fastui/fastui/json_schema.py b/src/python-fastui/fastui/json_schema.py index fa806cea..dce40451 100644 --- a/src/python-fastui/fastui/json_schema.py +++ b/src/python-fastui/fastui/json_schema.py @@ -213,7 +213,7 @@ def json_schema_array_to_fields( items_schema, required = deference_json_schema(items_schema, defs, required) for field_name in 'search_url', 'placeholder', 'description': if value := schema.get(field_name): - items_schema[field_name] = value # type: ignore + items_schema[field_name] = value # type: ignore (Could not assign item in TypedDict) if field := special_string_field(items_schema, loc_to_name(loc), title, required, True): yield field return @@ -318,7 +318,7 @@ def deference_json_schema( # copy everything except `anyOf` across to the new schema # TODO is this right? - for key, value in schema.items(): # type: ignore + for key, value in schema.items(): # type: ignore (schema type is union of unknwon and JsonSchemaAny) if key not in {'anyOf'}: not_null_schema[key] = value # type: ignore diff --git a/src/python-fastui/tests/test_auth_github.py b/src/python-fastui/tests/test_auth_github.py index 14016d3f..2560876a 100644 --- a/src/python-fastui/tests/test_auth_github.py +++ b/src/python-fastui/tests/test_auth_github.py @@ -6,7 +6,7 @@ from fastapi import FastAPI from fastui.auth import AuthError, GitHubAuthProvider, GitHubEmail -from fastui.auth.github import EXCHANGE_CACHE +from fastui.auth.github import EXCHANGE_CACHE, GitHubExchange from pydantic import SecretStr @@ -215,7 +215,10 @@ async def test_exchange_cached_purge(fake_github_app: FastAPI, httpx_client: htt assert len(EXCHANGE_CACHE) == 1 # manually add an old entry - EXCHANGE_CACHE._data['old'] = (datetime(2020, 1, 1), 'old_token') # type: ignore + EXCHANGE_CACHE._data['old'] = ( + datetime(2020, 1, 1), + GitHubExchange(access_token='old_token', token_type='bearer', scope=[]), + ) assert len(EXCHANGE_CACHE) == 2 await github_auth_provider.exchange_code('good') diff --git a/src/python-fastui/tests/test_components.py b/src/python-fastui/tests/test_components.py index dfa3acff..87f33f49 100644 --- a/src/python-fastui/tests/test_components.py +++ b/src/python-fastui/tests/test_components.py @@ -45,7 +45,7 @@ def test_root_model(): def test_root_model_single(): # fixed by validator - m = FastUI(root=components.Text(text='hello world')) # type: ignore + m = FastUI(root=[components.Text(text='hello world')]) assert m.model_dump(by_alias=True, exclude_none=True) == [ { 'text': 'hello world', @@ -55,7 +55,7 @@ def test_root_model_single(): def test_iframe(): - iframe = components.Iframe(src='https://www.example.com', srcdoc='

hello world

', sandbox='allow-scripts') # type: ignore + iframe = components.Iframe(src=Url('https://www.example.com'), srcdoc='

hello world

', sandbox='allow-scripts') assert iframe.model_dump(by_alias=True, exclude_none=True) == { 'src': Url('https://www.example.com'), 'type': 'Iframe', diff --git a/src/python-fastui/tests/test_forms.py b/src/python-fastui/tests/test_forms.py index b22e81cf..1b3620ca 100644 --- a/src/python-fastui/tests/test_forms.py +++ b/src/python-fastui/tests/test_forms.py @@ -96,7 +96,8 @@ async def test_simple_form_submit(): request = FakeRequest([('name', 'bar'), ('size', '123')]) - m = await form_dep.dependency(request) # type: ignore + assert form_dep.dependency is not None + m = await form_dep.dependency(request) assert isinstance(m, SimpleForm) assert m.model_dump() == {'name': 'bar', 'size': 123} @@ -107,7 +108,8 @@ async def test_simple_form_submit_repeat(): request = FakeRequest([('name', 'bar'), ('size', '123'), ('size', '456')]) with pytest.raises(HTTPException) as exc_info: - await form_dep.dependency(request) # type: ignore + assert form_dep.dependency is not None + await form_dep.dependency(request) # insert_assert(exc_info.value.detail) assert exc_info.value.detail == { @@ -157,8 +159,8 @@ async def test_w_nested_form_submit(): form_dep = fastui_form(FormWithNested) request = FakeRequest([('name', 'bar'), ('nested.x', '123')]) - - m = await form_dep.dependency(request) # type: ignore + assert form_dep.dependency is not None + m = await form_dep.dependency(request) assert isinstance(m, FormWithNested) assert m.model_dump() == {'name': 'bar', 'nested': {'x': 123}} @@ -192,7 +194,9 @@ async def test_file_submit(): file = UploadFile(BytesIO(b'foobar'), size=6, filename='testing.txt') request = FakeRequest([('profile_pic', file)]) - m = await fastui_form(FormWithFile).dependency(request) # type: ignore + form_dep = fastui_form(FormWithFile) + assert form_dep.dependency is not None + m = await form_dep.dependency(request) assert m.model_dump() == {'profile_pic': file} @@ -202,7 +206,9 @@ async def test_file_submit_repeat(): request = FakeRequest([('profile_pic', file1), ('profile_pic', file2)]) with pytest.raises(HTTPException) as exc_info: - await fastui_form(FormWithFile).dependency(request) # type: ignore + form_dep = fastui_form(FormWithFile) + assert form_dep.dependency is not None + await form_dep.dependency(request) # insert_assert(exc_info.value.detail) assert exc_info.value.detail == { @@ -241,7 +247,9 @@ async def test_file_constrained_submit(): file = UploadFile(BytesIO(b'foobar'), size=16_000, headers=headers) request = FakeRequest([('profile_pic', file)]) - m = await fastui_form(FormWithFileConstraint).dependency(request) # type: ignore + form_dep = fastui_form(FormWithFileConstraint) + assert form_dep.dependency is not None + m = await form_dep.dependency(request) assert m.model_dump() == {'profile_pic': file} @@ -249,7 +257,9 @@ async def test_file_constrained_submit_filename(): file = UploadFile(BytesIO(b'foobar'), size=16_000, filename='image.png') request = FakeRequest([('profile_pic', file)]) - m = await fastui_form(FormWithFileConstraint).dependency(request) # type: ignore + form_dep = fastui_form(FormWithFileConstraint) + assert form_dep.dependency is not None + m = await form_dep.dependency(request) assert m.model_dump() == {'profile_pic': file} @@ -259,7 +269,9 @@ async def test_file_constrained_submit_too_big(): request = FakeRequest([('profile_pic', file)]) with pytest.raises(HTTPException) as exc_info: - await fastui_form(FormWithFileConstraint).dependency(request) # type: ignore + form_dep = fastui_form(FormWithFileConstraint) + assert form_dep.dependency is not None + await form_dep.dependency(request) # insert_assert(exc_info.value.detail) assert exc_info.value.detail == { @@ -279,7 +291,9 @@ async def test_file_constrained_submit_wrong_type(): request = FakeRequest([('profile_pic', file)]) with pytest.raises(HTTPException) as exc_info: - await fastui_form(FormWithFileConstraint).dependency(request) # type: ignore + form_dep = fastui_form(FormWithFileConstraint) + assert form_dep.dependency is not None + await form_dep.dependency(request) # insert_assert(exc_info.value.detail) assert exc_info.value.detail == { @@ -325,7 +339,9 @@ async def test_multiple_files_single(): file = UploadFile(BytesIO(b'foobar'), size=16_000, filename='image.png') request = FakeRequest([('files', file)]) - m = await fastui_form(FormMultipleFiles).dependency(request) # type: ignore + form_dep = fastui_form(FormMultipleFiles) + assert form_dep.dependency is not None + m = await form_dep.dependency(request) assert m.model_dump() == {'files': [file]} @@ -334,7 +350,9 @@ async def test_multiple_files_multiple(): file2 = UploadFile(BytesIO(b'foobar'), size=6, filename='image2.png') request = FakeRequest([('files', file1), ('files', file2)]) - m = await fastui_form(FormMultipleFiles).dependency(request) # type: ignore + form_dep = fastui_form(FormMultipleFiles) + assert form_dep.dependency is not None + m = await form_dep.dependency(request) assert m.model_dump() == {'files': [file1, file2]} @@ -381,7 +399,9 @@ def test_fixed_tuple(): async def test_fixed_tuple_submit(): request = FakeRequest([('foo.0', 'bar'), ('foo.1', '123'), ('foo.2', '456')]) - m = await fastui_form(FixedTuple).dependency(request) # type: ignore + form_dep = fastui_form(FixedTuple) + assert form_dep.dependency is not None + m = await form_dep.dependency(request) assert m.model_dump() == {'foo': ('bar', 123, 456)} @@ -427,8 +447,9 @@ def test_fixed_tuple_nested(): async def test_fixed_tuple_nested_submit(): request = FakeRequest([('bar.foo.0', 'bar'), ('bar.foo.1', '123'), ('bar.foo.2', '456')]) - - m = await fastui_form(NestedTuple).dependency(request) # type: ignore + form_dep = fastui_form(NestedTuple) + assert form_dep.dependency is not None + m = await form_dep.dependency(request) assert m.model_dump() == {'bar': {'foo': ('bar', 123, 456)}}