Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fixed type issues and left detailed comments for unresolved issues
Browse files Browse the repository at this point in the history
ManiMozaffar committed May 1, 2024
1 parent f99f8e4 commit e71ebb1
Showing 10 changed files with 71 additions and 35 deletions.
2 changes: 1 addition & 1 deletion demo/pyproject.toml
Original file line number Diff line number Diff line change
@@ -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 = '[email protected]' }]
classifiers = [
2 changes: 1 addition & 1 deletion demo/src/auth_user.py
Original file line number Diff line number Diff line change
@@ -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:
5 changes: 3 additions & 2 deletions demo/src/components_list.py
Original file line number Diff line number Diff line change
@@ -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,
2 changes: 1 addition & 1 deletion demo/src/forms.py
Original file line number Diff line number Diff line change
@@ -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']
14 changes: 9 additions & 5 deletions demo/src/sse.py
Original file line number Diff line number Diff line change
@@ -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))
15 changes: 11 additions & 4 deletions demo/src/tables.py
Original file line number Diff line number Diff line change
@@ -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,
)
4 changes: 2 additions & 2 deletions src/python-fastui/fastui/json_schema.py
Original file line number Diff line number Diff line change
@@ -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

7 changes: 5 additions & 2 deletions src/python-fastui/tests/test_auth_github.py
Original file line number Diff line number Diff line change
@@ -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')
4 changes: 2 additions & 2 deletions src/python-fastui/tests/test_components.py
Original file line number Diff line number Diff line change
@@ -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='<p>hello world</p>', sandbox='allow-scripts') # type: ignore
iframe = components.Iframe(src=Url('https://www.example.com'), srcdoc='<p>hello world</p>', sandbox='allow-scripts')
assert iframe.model_dump(by_alias=True, exclude_none=True) == {
'src': Url('https://www.example.com'),
'type': 'Iframe',
51 changes: 36 additions & 15 deletions src/python-fastui/tests/test_forms.py
Original file line number Diff line number Diff line change
@@ -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,15 +247,19 @@ 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}


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)}}


0 comments on commit e71ebb1

Please sign in to comment.