Skip to content

Commit 8e9c085

Browse files
authored
Merge pull request #2 from ghost-in-moss/dev
initialize the project
2 parents f4705fd + 71eb4c7 commit 8e9c085

22 files changed

+39696
-0
lines changed

.gitignore

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
########################################################################
2+
# Python - https://github.com/github/gitignore/blob/master/Python.gitignore
3+
########################################################################
4+
# Byte-compiled / optimized / DLL files
5+
__pycache__/
6+
*.py[cod]
7+
*$py.class
8+
poetry.lock
9+
10+
# Distribution / packaging
11+
dist/
12+
eggs/
13+
.eggs/
14+
*.egg-info/
15+
*.egg
16+
17+
# Unit test / coverage reports
18+
.coverage
19+
.coverage\.*
20+
.pytest_cache/
21+
.mypy_cache/
22+
test-reports
23+
24+
# Test fixtures
25+
cffi_bin
26+
27+
# Pyenv Stuff
28+
.python-version
29+
30+
########################################################################
31+
# OSX - https://github.com/github/gitignore/blob/master/Global/macOS.gitignore
32+
########################################################################
33+
.DS_Store
34+
.DocumentRevisions-V100
35+
.fseventsd
36+
.Spotlight-V100
37+
.TemporaryItems
38+
.Trashes
39+
.VolumeIcon.icns
40+
.com.apple.timemachine.donotpresent
41+
42+
########################################################################
43+
# node - https://github.com/github/gitignore/blob/master/Node.gitignore
44+
########################################################################
45+
# Logs
46+
npm-debug.log*
47+
yarn-debug.log*
48+
yarn-error.log*
49+
50+
# Dependency directories
51+
node_modules/
52+
53+
# Coverage directory used by tools like istanbul
54+
coverage/
55+
56+
# Lockfiles
57+
template/**/yarn.lock
58+
template/**/package-lock.json
59+
template-reactless/**/yarn.lock
60+
template-reactless/**/package-lock.json
61+
62+
########################################################################
63+
# JetBrains
64+
########################################################################
65+
.idea
66+
67+
########################################################################
68+
# VSCode
69+
########################################################################
70+
.vscode/
71+
buildcontext/

MANIFEST.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
recursive-include streamlit_react_jsonschema/frontend/build *

README.md

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# Streamlit-react-jsonschema
2+
3+
Streamlit component,
4+
building form by [react-jsonschema-form](https://react-jsonschema-form.readthedocs.io/).
5+
6+
This library is experimental for [GhostOS](https://github.com/ghost-in-moss/GhostOS) yet.
7+
8+
## Installation
9+
10+
install `streamlit-react-jsonschema` with pip
11+
12+
```bash
13+
pip install streamlit_react_jsonschema
14+
```
15+
16+
## Example
17+
18+
see [baseline example](examples/baseline.py)
19+
20+
```python
21+
from typing import Union, List, Dict
22+
import streamlit as st
23+
import streamlit_react_jsonschema as srj
24+
import inspect
25+
from pydantic import BaseModel, Field
26+
27+
28+
class Student(BaseModel):
29+
name: str = Field(description="name of the student")
30+
level: int = Field(description="level of the student")
31+
32+
33+
class SomeClass(BaseModel):
34+
"""
35+
test class of pydantic model Foo
36+
"""
37+
name: Union[str, None] = Field(None, description="filed with optional[str]")
38+
age: int = Field(0, description="bar value")
39+
some_float: float = Field(description="test about float value")
40+
41+
students: List[Student] = Field(default_factory=list, description="list of students")
42+
students_dict: Dict[str, Student] = Field(default_factory=dict, description="dict of students")
43+
44+
45+
st.title("Streamlit-react-jsonschema Example")
46+
with st.expander("source code of the example pydantic class", expanded=True):
47+
codes = inspect.getsource(SomeClass)
48+
st.code(codes)
49+
50+
value, submitted = srj.pydantic_form(model=SomeClass)
51+
52+
st.subheader("result:")
53+
st.write(f"submitted: {submitted}")
54+
st.write(f"type of the result: {type(value)}")
55+
st.write(value)
56+
if isinstance(value, BaseModel):
57+
st.write("model dump value")
58+
st.write(value.model_dump(exclude_defaults=False))
59+
```
60+
61+
How to run this example? Check [streamlit](https://docs.streamlit.io/) first~
62+
63+
## Dependencies
64+
65+
- [Streamlit](https://streamlit.io/)
66+
- [React-jsonschema-form](https://react-jsonschema-form.readthedocs.io/): render the form from json schema
67+
- [Material UI](https://mui.com/) : use `@rjsf/mui` theme from the `react-jsonschema-form`
68+
- [pydantic](https://docs.pydantic.dev/latest/) : generate json schema
69+
70+

examples/baseline.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
from typing import Union, List, Dict
2+
import streamlit as st
3+
import streamlit_react_jsonschema as srj
4+
import inspect
5+
from pydantic import BaseModel, Field
6+
7+
8+
class Student(BaseModel):
9+
name: str = Field(description="name of the student")
10+
level: int = Field(description="level of the student")
11+
12+
13+
class SomeClass(BaseModel):
14+
"""
15+
test class of pydantic model Foo
16+
"""
17+
name: Union[str, None] = Field(None, description="filed with optional[str]")
18+
age: int = Field(0, description="bar value")
19+
some_float: float = Field(description="test about float value")
20+
21+
students: List[Student] = Field(default_factory=list, description="list of students")
22+
students_dict: Dict[str, Student] = Field(default_factory=dict, description="dict of students")
23+
24+
25+
st.title("Streamlit-react-jsonschema Example")
26+
with st.expander("source code of the example pydantic class", expanded=True):
27+
codes = inspect.getsource(SomeClass)
28+
st.code(codes)
29+
30+
value, submitted = srj.pydantic_form(model=SomeClass)
31+
32+
st.subheader("result:")
33+
st.write(f"submitted: {submitted}")
34+
st.write(f"type of the result: {type(value)}")
35+
st.write(value)
36+
if isinstance(value, BaseModel):
37+
st.write("model dump value")
38+
st.write(value.model_dump(exclude_defaults=False))

pyproject.toml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
[tool.poetry]
2+
name = "streamlit_react_jsonschema"
3+
version = "0.1.3"
4+
description = "streamlit component for GhostOS, render form from JSONSchema by react-jsonschema-component"
5+
authors = ["ZhuMing <[email protected]>"]
6+
license = "MIT"
7+
readme = "README.md"
8+
9+
[tool.poetry.dependencies]
10+
python = "!=3.9.7,>=3.8"
11+
pydantic = "^2"
12+
streamlit = "^1.39.0"
13+
14+
15+
16+
[build-system]
17+
requires = ["poetry-core"]
18+
build-backend = "poetry.core.masonry.api"
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import os
2+
import streamlit as st
3+
import streamlit.components.v1 as components
4+
from typing import Type, Dict, Optional, Tuple
5+
from pydantic import BaseModel
6+
7+
__all__ = [
8+
'jsonschema_form',
9+
'pydantic_form',
10+
'pydantic_instance_form',
11+
]
12+
13+
_RELEASE = True
14+
15+
COMPONENT_NAME = "streamlit_react_jsonschema"
16+
17+
if not _RELEASE:
18+
_component_func = components.declare_component(
19+
COMPONENT_NAME,
20+
url="http://localhost:3001",
21+
)
22+
else:
23+
parent_dir = os.path.dirname(os.path.abspath(__file__))
24+
build_dir = os.path.join(parent_dir, "frontend/build")
25+
_component_func = components.declare_component(COMPONENT_NAME, path=build_dir)
26+
27+
28+
def pydantic_form(
29+
model: Type[BaseModel],
30+
*,
31+
key: str = None,
32+
default: Dict = None,
33+
) -> Tuple[Optional[BaseModel], bool]:
34+
"""
35+
render a react-json-schema form by json schema from pydantic model
36+
default ui is material ui v5
37+
see details in frontend/
38+
:param model: the pydantic model type
39+
:param key: the elementId of the Form
40+
:param default: default value of the model.
41+
:return: (model instance: BaseModel, submitted: bool)
42+
model instance is None unless the form is submitted, get the pydantic model instance.
43+
this function use pydantic model to validate the result that form returns.
44+
"""
45+
if key is None:
46+
key = _pydantic_model_key(model)
47+
schema = model.model_json_schema()
48+
result, submitted = jsonschema_form(key, schema, default=default)
49+
if result is not None:
50+
return model(**result), submitted is True
51+
return result, submitted is True
52+
53+
54+
def _pydantic_model_key(model: Type[BaseModel]) -> str:
55+
return f"{model.__module__}:{model.__qualname__}"
56+
57+
58+
def pydantic_instance_form(
59+
instance: BaseModel,
60+
*,
61+
key: str = None,
62+
deep: bool = True,
63+
) -> Tuple[Optional[BaseModel], bool]:
64+
"""
65+
render a react-json-schema form by json schema from pydantic instance
66+
default ui is material ui v5
67+
:param instance: pydantic model instance
68+
:param key: the elementId of the Form
69+
:param deep: if deep is True, return instance's deep copy by updated values
70+
:return: (instance, submitted)
71+
instance is None unless the form is submitted, get the pydantic model instance.
72+
"""
73+
data = instance.model_dump(exclude_defaults=False)
74+
schema = instance.model_json_schema()
75+
model = type(instance)
76+
if key is None:
77+
key = _pydantic_model_key(model)
78+
result, submitted = jsonschema_form(key, schema, default=data)
79+
if result is None:
80+
return None, submitted is True
81+
return instance.model_copy(update=result, deep=deep), submitted is True
82+
83+
84+
def jsonschema_form(
85+
key: str,
86+
schema: Dict,
87+
*,
88+
default: Dict = None,
89+
) -> Tuple[Optional[Dict], bool]:
90+
"""
91+
render a react-json-schema form by raw json schema
92+
:param key: the elementId of the Form
93+
:param schema: the json schema
94+
:param default: default value of the schema
95+
:return: None unless the form is submitted, get the dict value of the formData
96+
"""
97+
if default is None:
98+
default = {}
99+
with st.container(border=True):
100+
component_value = _component_func(
101+
key=key,
102+
schema=schema,
103+
formData=default,
104+
)
105+
if isinstance(component_value, dict):
106+
return component_value.get("formData", {}), component_value.get("submitted", False)
107+
return None, False
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Run the component's dev server on :3001
2+
# (The Streamlit dev server already runs on :3000)
3+
PORT=3001
4+
5+
# Don't automatically open the web browser on `npm run start`.
6+
BROWSER=none
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"endOfLine": "lf",
3+
"semi": false,
4+
"trailingComma": "es5"
5+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"files": {
3+
"main.js": "./static/js/main.1978918c.js",
4+
"index.html": "./index.html",
5+
"main.1978918c.js.map": "./static/js/main.1978918c.js.map"
6+
},
7+
"entrypoints": [
8+
"static/js/main.1978918c.js"
9+
]
10+
}

0 commit comments

Comments
 (0)