Skip to content

Commit 0ca4e58

Browse files
committed
[feat] Add v2 cookiecutter support
1 parent ff9a3e5 commit 0ca4e58

File tree

37 files changed

+1079
-45
lines changed

37 files changed

+1079
-45
lines changed

.github/replay-files/v2/template.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
"author_name": "John Smith",
44
"author_email": "[email protected]",
55
"project_name": "Streamlit Component X",
6-
"package_name": "streamlit-component-x",
7-
"import_name": "streamlit_component_x",
8-
"description": "Streamlit component that v2 component template",
6+
"package_name": "streamlit-custom-component",
7+
"import_name": "my_component",
8+
"description": "Streamlit component that allows you to do X",
99
"open_source_license": "MIT license",
1010
"framework": "React + Typescript"
1111
}

cookiecutter/v1/{{ cookiecutter.package_name }}/e2e/test_template.py

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
from pathlib import Path
22

33
import pytest
4-
5-
from playwright.sync_api import Page, expect
6-
74
from e2e_utils import StreamlitRunner
5+
from playwright.sync_api import Page, expect
86

97
ROOT_DIRECTORY = Path(__file__).parent.parent.absolute()
108
BASIC_EXAMPLE_FILE = ROOT_DIRECTORY / "my_component" / "example.py"
119

10+
1211
@pytest.fixture(autouse=True, scope="module")
1312
def streamlit_app():
1413
with StreamlitRunner(BASIC_EXAMPLE_FILE) as runner:
@@ -23,15 +22,11 @@ def go_to_app(page: Page, streamlit_app: StreamlitRunner):
2322

2423

2524
def test_should_render_template(page: Page):
26-
frame_0 = page.frame_locator(
27-
'iframe[title="my_component\\.my_component"]'
28-
).nth(0)
29-
frame_1 = page.frame_locator(
30-
'iframe[title="my_component\\.my_component"]'
31-
).nth(1)
25+
frame_0 = page.frame_locator('iframe[title="my_component\\.my_component"]').nth(0)
26+
frame_1 = page.frame_locator('iframe[title="my_component\\.my_component"]').nth(1)
3227

33-
st_markdown_0 = page.get_by_role('paragraph').nth(0)
34-
st_markdown_1 = page.get_by_role('paragraph').nth(1)
28+
st_markdown_0 = page.get_by_role("paragraph").nth(0)
29+
st_markdown_1 = page.get_by_role("paragraph").nth(1)
3530

3631
expect(st_markdown_0).to_contain_text("You've clicked 0 times!")
3732

@@ -66,7 +61,7 @@ def test_should_change_iframe_height(page: Page):
6661
locator = page.locator('iframe[title="my_component\\.my_component"]').nth(1)
6762

6863
page.wait_for_timeout(1000)
69-
init_frame_height = locator.bounding_box()['height']
64+
init_frame_height = locator.bounding_box()["height"]
7065
assert init_frame_height != 0
7166

7267
page.get_by_label("Enter a name").click()
@@ -77,13 +72,13 @@ def test_should_change_iframe_height(page: Page):
7772
expect(frame.get_by_text("Streamlit Streamlit Streamlit")).to_be_visible()
7873

7974
page.wait_for_timeout(1000)
80-
frame_height = locator.bounding_box()['height']
75+
frame_height = locator.bounding_box()["height"]
8176
assert frame_height > init_frame_height
8277

8378
page.set_viewport_size({"width": 150, "height": 150})
8479

8580
expect(frame.get_by_text("Streamlit Streamlit Streamlit")).not_to_be_in_viewport()
8681

8782
page.wait_for_timeout(1000)
88-
frame_height_after_viewport_change = locator.bounding_box()['height']
83+
frame_height_after_viewport_change = locator.bounding_box()["height"]
8984
assert frame_height_after_viewport_change > frame_height

cookiecutter/v2/README.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Streamlit template for CookieCutter
2+
3+
Template for creating a streamlit component
4+
5+
## Usage
6+
7+
### Run Cookiecutter via uvx
8+
9+
```
10+
uvx cookiecutter https://github.com/streamlit/component-template.git --directory=cookiecutter/v2
11+
```
12+
13+
Follow the prompts to generate your project.
14+
15+
More info:
16+
17+
- Cookiecutter: `https://pypi.org/project/cookiecutter/`
18+
- uv tools: `https://docs.astral.sh/uv/guides/tools/`

cookiecutter/v2/cookiecutter.json

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"author_name": "John Smith",
3+
"author_email": "[email protected]",
4+
"project_name": "Streamlit Component X",
5+
"package_name": "{{ cookiecutter.project_name.lower().replace(' ', '-') }}",
6+
"import_name": "{{ cookiecutter.package_name.lower().replace('-', '_') }}",
7+
"description": "Streamlit component that allows you to do X",
8+
"open_source_license": [
9+
"MIT license",
10+
"BSD license",
11+
"ISC license",
12+
"Apache Software License 2.0",
13+
"GNU General Public License v3",
14+
"Not open source"
15+
],
16+
"framework": [
17+
"React + Typescript",
18+
"Pure Typescript"
19+
]
20+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import shutil
2+
from pathlib import Path
3+
4+
project_dir = Path("{{ cookiecutter.import_name }}").absolute()
5+
6+
framework = "{{ cookiecutter.framework }}"
7+
if framework == "React + Typescript":
8+
shutil.move(str(project_dir / "frontend-react"), str(project_dir / "frontend"))
9+
shutil.rmtree(str(project_dir / "frontend-reactless"))
10+
elif framework == "Pure Typescript":
11+
shutil.move(str(project_dir / "frontend-reactless"), str(project_dir / "frontend"))
12+
shutil.rmtree(str(project_dir / "frontend-react"))
13+
else:
14+
raise Exception(f"Unsupported option: {framework!r}")
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
Copyright (c) 2018-2021 Streamlit Inc.
2+
3+
Permission is hereby granted, free of charge, to any person obtaining a copy
4+
of this software and associated documentation files (the "Software"), to deal
5+
in the Software without restriction, including without limitation the rights
6+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7+
copies of the Software, and to permit persons to whom the Software is
8+
furnished to do so, subject to the following conditions:
9+
10+
The above copyright notice and this permission notice shall be included in all
11+
copies or substantial portions of the Software.
12+
13+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19+
SOFTWARE.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
recursive-include {{ cookiecutter.import_name }}/frontend/build *
2+
include pyproject.toml
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# {{ cookiecutter.package_name }}
2+
3+
{{ cookiecutter.description }}
4+
5+
## Installation instructions
6+
7+
```sh
8+
pip install {{ cookiecutter.package_name }}
9+
```
10+
11+
## Usage instructions
12+
13+
```python
14+
import streamlit as st
15+
16+
from {{ cookiecutter.import_name }} import {{ cookiecutter.import_name }}
17+
18+
value = {{ cookiecutter.import_name }}()
19+
20+
st.write(value)
21+
```
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import streamlit as st
2+
from {{ cookiecutter.import_name }} import {{ cookiecutter.import_name }}
3+
4+
# Add some test code to play with the component while it's in development.
5+
# During development, we can run this just as we would any other Streamlit
6+
# app: `$ streamlit run {{ cookiecutter.import_name }}/example.py`
7+
8+
st.subheader("Component with constant args")
9+
10+
# Create an instance of our component with a constant `name` arg, and
11+
# print its output value.
12+
result = {{ cookiecutter.import_name }}("World")
13+
st.markdown("You've clicked %s times!" % int(result["num_clicks"]))
14+
15+
st.markdown("---")
16+
st.subheader("Component with variable args")
17+
18+
# Create a second instance of our component whose `name` arg will vary
19+
# based on a text_input widget.
20+
#
21+
# We use the special "key" argument to assign a fixed identity to this
22+
# component instance. By default, when a component's arguments change,
23+
# it is considered a new instance and will be re-mounted on the frontend
24+
# and lose its current state. In this case, we want to vary the component's
25+
# "name" argument without having it get recreated.
26+
name_input = st.text_input("Enter a name", value="Streamlit")
27+
result = {{ cookiecutter.import_name }}(name_input, key="foo")
28+
st.markdown("You've clicked %s times!" % int(result["num_clicks"]))
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
[build-system]
2+
requires = ["setuptools>=61.0", "wheel"]
3+
build-backend = "setuptools.build_meta"
4+
5+
[project]
6+
name = "{{ cookiecutter.package_name }}"
7+
version = "0.0.1"
8+
description = "{{ cookiecutter.description }}"
9+
readme = "README.md"
10+
requires-python = ">=3.10"
11+
authors = [
12+
{ name = "{{ cookiecutter.author_name }}", email = "{{ cookiecutter.author_email }}" },
13+
]
14+
# TODO: Restore this
15+
# dependencies = ["streamlit >= 1.51"]
16+
17+
[project.optional-dependencies]
18+
devel = [
19+
"wheel",
20+
"pytest==7.4.0",
21+
"playwright==1.48.0",
22+
"requests==2.31.0",
23+
"pytest-playwright-snapshot==1.0",
24+
"pytest-rerunfailures==12.0",
25+
]
26+
27+
[project.license]
28+
file = "LICENSE"
29+
30+
[tool.setuptools.packages.find]
31+
include = ["{{ cookiecutter.import_name }}*"]
32+
33+
[tool.setuptools]
34+
include-package-data = true
35+
36+
[tool.setuptools.package-data]
37+
{{ cookiecutter.import_name }} = ["frontend/build/**/*", "pyproject.toml"]
38+
39+
[[tool.streamlit.component.components]]
40+
name = "{{ cookiecutter.import_name }}"
41+
asset_dir = "{{ cookiecutter.import_name }}/frontend/build"

0 commit comments

Comments
 (0)