4 | Streamlit Component
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/streamlit_highcharts/frontend/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "streamlit_highcharts",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "react": "^16.13.1",
7 | "react-dom": "^16.13.1",
8 | "streamlit-component-lib": "^2.0.0"
9 | },
10 | "scripts": {
11 | "start": "react-scripts start",
12 | "build": "react-scripts build",
13 | "test": "react-scripts test",
14 | "eject": "react-scripts eject"
15 | },
16 | "eslintConfig": {
17 | "extends": "react-app"
18 | },
19 | "browserslist": {
20 | "production": [
21 | ">0.2%",
22 | "not dead",
23 | "not op_mini all"
24 | ],
25 | "development": [
26 | "last 1 chrome version",
27 | "last 1 firefox version",
28 | "last 1 safari version"
29 | ]
30 | },
31 | "homepage": ".",
32 | "devDependencies": {
33 | "@types/node": "^12.0.0",
34 | "@types/react": "^16.9.0",
35 | "@types/react-dom": "^16.9.0",
36 | "react-scripts": "^5.0.1",
37 | "typescript": "^4.2.0"
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 |
3 | import setuptools
4 |
5 | this_directory = Path(__file__).parent
6 | long_description = (this_directory / "README.md").read_text()
7 |
8 | setuptools.setup(
9 | name="streamlit-highcharts-python",
10 | version="0.0.1",
11 | author="HCP LLC",
12 | author_email="support@highchartspython.com",
13 | description="Streamlit component that allows you to embed Highcharts for Python data visualizations in your Streamlit apps.",
14 | long_description=long_description,
15 | long_description_content_type="text/markdown",
16 | url="",
17 | packages=setuptools.find_packages(),
18 | include_package_data=True,
19 | classifiers=[],
20 | python_requires=">=3.7",
21 | install_requires=[
22 | # By definition, a Custom Component depends on Streamlit.
23 | # If your component has other Python dependencies, list
24 | # them here.
25 | "streamlit >= 0.63",
26 | ],
27 | extras_require={
28 | "devel": [
29 | "wheel",
30 | "pytest==7.4.0",
31 | "playwright==1.39.0",
32 | "requests==2.31.0",
33 | "pytest-playwright-snapshot==1.0",
34 | "pytest-rerunfailures==12.0",
35 | ]
36 | }
37 | )
38 |
--------------------------------------------------------------------------------
/streamlit_highcharts/example.py:
--------------------------------------------------------------------------------
1 | import streamlit as st
2 | from streamlit_highcharts import streamlit_highcharts
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 streamlit_highcharts/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 | num_clicks = streamlit_highcharts("World")
13 | st.markdown("You've clicked %s times!" % int(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 | num_clicks = streamlit_highcharts(name_input, key="foo")
28 | st.markdown("You've clicked %s times!" % int(num_clicks))
29 |
--------------------------------------------------------------------------------
/streamlit_highcharts/frontend/src/MyComponent.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Streamlit,
3 | StreamlitComponentBase,
4 | withStreamlitConnection,
5 | } from "streamlit-component-lib"
6 | import React, { ReactNode } from "react"
7 |
8 | interface State {
9 | numClicks: number
10 | isFocused: boolean
11 | }
12 |
13 | /**
14 | * This is a React-based component template. The `render()` function is called
15 | * automatically when your component should be re-rendered.
16 | */
17 | class MyComponent extends StreamlitComponentBase {
18 | public state = { numClicks: 0, isFocused: false }
19 |
20 | public render = (): ReactNode => {
21 | // Arguments that are passed to the plugin in Python are accessible
22 | // via `this.props.args`. Here, we access the "name" arg.
23 | const name = this.props.args["name"]
24 |
25 | // Streamlit sends us a theme object via props that we can use to ensure
26 | // that our component has visuals that match the active theme in a
27 | // streamlit app.
28 | const { theme } = this.props
29 | const style: React.CSSProperties = {}
30 |
31 | // Maintain compatibility with older versions of Streamlit that don't send
32 | // a theme object.
33 | if (theme) {
34 | // Use the theme object to style our button border. Alternatively, the
35 | // theme style is defined in CSS vars.
36 | const borderStyling = `1px solid ${
37 | this.state.isFocused ? theme.primaryColor : "gray"
38 | }`
39 | style.border = borderStyling
40 | style.outline = borderStyling
41 | }
42 |
43 | // Show a button and some text.
44 | // When the button is clicked, we'll increment our "numClicks" state
45 | // variable, and send its new value back to Streamlit, where it'll
46 | // be available to the Python program.
47 | return (
48 |
49 | Hello, {name}!
50 |
59 |
60 | )
61 | }
62 |
63 | /** Click handler for our "Click Me!" button. */
64 | private onClicked = (): void => {
65 | // Increment state.numClicks, and pass the new value back to
66 | // Streamlit via `Streamlit.setComponentValue`.
67 | this.setState(
68 | prevState => ({ numClicks: prevState.numClicks + 1 }),
69 | () => Streamlit.setComponentValue(this.state.numClicks)
70 | )
71 | }
72 |
73 | /** Focus handler for our "Click Me!" button. */
74 | private _onFocus = (): void => {
75 | this.setState({ isFocused: true })
76 | }
77 |
78 | /** Blur handler for our "Click Me!" button. */
79 | private _onBlur = (): void => {
80 | this.setState({ isFocused: false })
81 | }
82 | }
83 |
84 | // "withStreamlitConnection" is a wrapper function. It bootstraps the
85 | // connection between your component and the Streamlit app, and handles
86 | // passing arguments from Python -> Component.
87 | //
88 | // You don't need to edit withStreamlitConnection (but you're welcome to!).
89 | export default withStreamlitConnection(MyComponent)
90 |
--------------------------------------------------------------------------------
/e2e/test_template.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 |
3 | import pytest
4 |
5 | from playwright.sync_api import Page, expect
6 |
7 | from e2e_utils import StreamlitRunner
8 |
9 | ROOT_DIRECTORY = Path(__file__).parent.parent.absolute()
10 | BASIC_EXAMPLE_FILE = ROOT_DIRECTORY / "my_component" / "example.py"
11 |
12 | @pytest.fixture(autouse=True, scope="module")
13 | def streamlit_app():
14 | with StreamlitRunner(BASIC_EXAMPLE_FILE) as runner:
15 | yield runner
16 |
17 |
18 | @pytest.fixture(autouse=True, scope="function")
19 | def go_to_app(page: Page, streamlit_app: StreamlitRunner):
20 | page.goto(streamlit_app.server_url)
21 | # Wait for app to load
22 | page.get_by_role("img", name="Running...").is_hidden()
23 |
24 |
25 | 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)
32 |
33 | st_markdown_0 = page.get_by_role('paragraph').nth(0)
34 | st_markdown_1 = page.get_by_role('paragraph').nth(1)
35 |
36 | expect(st_markdown_0).to_contain_text("You've clicked 0 times!")
37 |
38 | frame_0.get_by_role("button", name="Click me!").click()
39 |
40 | expect(st_markdown_0).to_contain_text("You've clicked 1 times!")
41 | expect(st_markdown_1).to_contain_text("You've clicked 0 times!")
42 |
43 | frame_1.get_by_role("button", name="Click me!").click()
44 | frame_1.get_by_role("button", name="Click me!").click()
45 |
46 | expect(st_markdown_0).to_contain_text("You've clicked 1 times!")
47 | expect(st_markdown_1).to_contain_text("You've clicked 2 times!")
48 |
49 | page.get_by_label("Enter a name").click()
50 | page.get_by_label("Enter a name").fill("World")
51 | page.get_by_label("Enter a name").press("Enter")
52 |
53 | expect(frame_1.get_by_text("Hello, World!")).to_be_visible()
54 |
55 | frame_1.get_by_role("button", name="Click me!").click()
56 |
57 | expect(st_markdown_0).to_contain_text("You've clicked 1 times!")
58 | expect(st_markdown_1).to_contain_text("You've clicked 3 times!")
59 |
60 |
61 | def test_should_change_iframe_height(page: Page):
62 | frame = page.frame_locator('iframe[title="my_component\\.my_component"]').nth(1)
63 |
64 | expect(frame.get_by_text("Hello, Streamlit!")).to_be_visible()
65 |
66 | locator = page.locator('iframe[title="my_component\\.my_component"]').nth(1)
67 |
68 | page.wait_for_timeout(1000)
69 | init_frame_height = locator.bounding_box()['height']
70 | assert init_frame_height != 0
71 |
72 | page.get_by_label("Enter a name").click()
73 |
74 | page.get_by_label("Enter a name").fill(35 * "Streamlit ")
75 | page.get_by_label("Enter a name").press("Enter")
76 |
77 | expect(frame.get_by_text("Streamlit Streamlit Streamlit")).to_be_visible()
78 |
79 | page.wait_for_timeout(1000)
80 | frame_height = locator.bounding_box()['height']
81 | assert frame_height > init_frame_height
82 |
83 | page.set_viewport_size({"width": 150, "height": 150})
84 |
85 | expect(frame.get_by_text("Streamlit Streamlit Streamlit")).not_to_be_in_viewport()
86 |
87 | page.wait_for_timeout(1000)
88 | frame_height_after_viewport_change = locator.bounding_box()['height']
89 | assert frame_height_after_viewport_change > frame_height
90 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [build-system]
2 | requires = [
3 | "hatchling>=1.6",
4 | ]
5 | build-backend = "hatchling.build"
6 |
7 | [project]
8 | name = "streamlit-highcharts"
9 | description = "Streamlit component that for embedding Highcharts for Python data visualizations in your Streamlit apps."
10 | readme = "README.md"
11 | license-files = { paths = ["LICENSE"] }
12 |
13 | dynamic = [
14 | "version"
15 | ]
16 |
17 | authors = [
18 | { name = "HCP LLC", email = "support@highchartspython.com" }
19 | ]
20 |
21 | keywords = [
22 | "highcharts",
23 | "data visualization",
24 | "data viz",
25 | "charts",
26 | "graphing",
27 | "streamlit",
28 | "plotting"
29 | ]
30 |
31 | classifiers = [
32 | "Development Status :: 5 - Production/Stable",
33 | "Environment :: Web Environment",
34 | "Operating System :: OS Independent",
35 | "Intended Audience :: Developers",
36 | "Intended Audience :: Education",
37 | "Intended Audience :: Financial and Insurance Industry",
38 | "Intended Audience :: Healthcare Industry",
39 | "Intended Audience :: Manufacturing",
40 | "Intended Audience :: Science/Research",
41 |
42 | "Topic :: Software Development :: Libraries",
43 | "Topic :: Software Development :: Code Generators",
44 | "Topic :: Utilities",
45 | "Topic :: Internet :: WWW/HTTP :: Dynamic Content",
46 | "Topic :: Office/Business",
47 | "Topic :: Scientific/Engineering :: Visualization",
48 | "Topic :: Scientific/Engineering :: Information Analysis",
49 |
50 | "License :: Other/Proprietary License"
51 | ]
52 |
53 | requires-python = ">= 3.10"
54 | dependencies = [
55 | "highcharts-core>=1.9.3",
56 | "streamlit >= 0.63",
57 | ]
58 |
59 | [tool.hatch.version]
60 | path = "streamlit_highcharts/__version__.py"
61 |
62 | [project.urls]
63 | "Homepage" = "https://highchartspython.com"
64 | "Documentation" = "https://streamlit.highchartspython.com/en/latest/"
65 | "Support" = "https://www.highchartspython.com/get-help"
66 | "Source Code" = "https://github.com/highcharts-for-python/streamlit-highcharts-python"
67 | "History" = "https://github.com/highcharts-for-python/streamlit-highcharts-python/blob/main/CHANGES.rst"
68 | "Bug Tracker" = "https://github.com/highcharts-for-python/streamlit-highcharts-python/issues"
69 | "Demo" = "https://github.com/highcharts-for-python/highcharts-for-python-demos"
70 | "Sponsor" = "https://github.com/sponsors/highcharts-for-python"
71 |
72 | [project.optional-dependencies]
73 | dev = [
74 | "playwright==1.39.0",
75 | "pytest>=7.4.0",
76 | "pytest-cov>=3.0.0",
77 | "pytest-xdist>=2.5.0",
78 | "python-dotenv>=1.0.0",
79 | "pytest-playwright-snapshot==1.0",
80 | "pytest-rerunfailures==12.0",
81 | "requests==2.31.0",
82 | "Sphinx==6.1.3",
83 | "sphinx-rtd-theme==1.2.0",
84 | "sphinx-toolbox==3.4.0",
85 | "sphinx-tabs==3.4.1",
86 | ]
87 | soft = [
88 | "IPython>=8.10.0",
89 | "pyspark>=3.3.0",
90 | "pandas>=1.3.3",
91 | "orjson>=3.7.7",
92 | "anthropic>=0.3.11",
93 | "dill>=0.3.7",
94 | "openai>=0.28.0"
95 | ]
96 | ai = [
97 | "anthropic>=0.3.11",
98 | "dill>=0.3.7",
99 | "openai>=0.28.0"
100 | ]
101 | docs = [
102 | "Sphinx==6.1.3",
103 | "sphinx-rtd-theme==1.2.0",
104 | "sphinx-toolbox==3.4.0",
105 | "sphinx-tabs==3.4.1"
106 | ]
--------------------------------------------------------------------------------
/streamlit_highcharts/__init__.py:
--------------------------------------------------------------------------------
1 | import os
2 | import streamlit.components.v1 as components
3 |
4 | # Create a _RELEASE constant. We'll set this to False while we're developing
5 | # the component, and True when we're ready to package and distribute it.
6 | # (This is, of course, optional - there are innumerable ways to manage your
7 | # release process.)
8 | _RELEASE = False
9 |
10 | # Declare a Streamlit component. `declare_component` returns a function
11 | # that is used to create instances of the component. We're naming this
12 | # function "_component_func", with an underscore prefix, because we don't want
13 | # to expose it directly to users. Instead, we will create a custom wrapper
14 | # function, below, that will serve as our component's public API.
15 |
16 | # It's worth noting that this call to `declare_component` is the
17 | # *only thing* you need to do to create the binding between Streamlit and
18 | # your component frontend. Everything else we do in this file is simply a
19 | # best practice.
20 |
21 | if not _RELEASE:
22 | _component_func = components.declare_component(
23 | # We give the component a simple, descriptive name ("streamlit_highcharts"
24 | # does not fit this bill, so please choose something better for your
25 | # own component :)
26 | "streamlit_highcharts",
27 | # Pass `url` here to tell Streamlit that the component will be served
28 | # by the local dev server that you run via `npm run start`.
29 | # (This is useful while your component is in development.)
30 | url="http://localhost:3001",
31 | )
32 | else:
33 | # When we're distributing a production version of the component, we'll
34 | # replace the `url` param with `path`, and point it to the component's
35 | # build directory:
36 | parent_dir = os.path.dirname(os.path.abspath(__file__))
37 | build_dir = os.path.join(parent_dir, "frontend/build")
38 | _component_func = components.declare_component("streamlit_highcharts", path=build_dir)
39 |
40 |
41 | # Create a wrapper function for the component. This is an optional
42 | # best practice - we could simply expose the component function returned by
43 | # `declare_component` and call it done. The wrapper allows us to customize
44 | # our component's API: we can pre-process its input args, post-process its
45 | # output value, and add a docstring for users.
46 | def streamlit_highcharts(name, key=None):
47 | """Create a new instance of "streamlit_highcharts".
48 |
49 | Parameters
50 | ----------
51 | name: str
52 | The name of the thing we're saying hello to. The component will display
53 | the text "Hello, {name}!"
54 | key: str or None
55 | An optional key that uniquely identifies this component. If this is
56 | None, and the component's arguments are changed, the component will
57 | be re-mounted in the Streamlit frontend and lose its current state.
58 |
59 | Returns
60 | -------
61 | int
62 | The number of times the component's "Click Me" button has been clicked.
63 | (This is the value passed to `Streamlit.setComponentValue` on the
64 | frontend.)
65 |
66 | """
67 | # Call through to our private component function. Arguments we pass here
68 | # will be sent to the frontend, where they'll be available in an "args"
69 | # dictionary.
70 | #
71 | # "default" is a special argument that specifies the initial return
72 | # value of the component before the user has interacted with it.
73 | component_value = _component_func(name=name, key=key, default=0)
74 |
75 | # We could modify the value returned from the component if we wanted.
76 | # There's no need to do this in our simple example - but it's an option.
77 | return component_value
78 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | wheels/
23 | share/python-wheels/
24 | *.egg-info/
25 | .installed.cfg
26 | *.egg
27 | MANIFEST
28 | streamlit_highcharts/frontend/node_modules/
29 |
30 | # PyInstaller
31 | # Usually these files are written by a python script from a template
32 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
33 | *.manifest
34 | *.spec
35 |
36 | # Installer logs
37 | pip-log.txt
38 | pip-delete-this-directory.txt
39 |
40 | # Unit test / coverage reports
41 | htmlcov/
42 | .tox/
43 | .nox/
44 | .coverage
45 | .coverage.*
46 | .cache
47 | nosetests.xml
48 | coverage.xml
49 | *.cover
50 | *.py,cover
51 | .hypothesis/
52 | .pytest_cache/
53 | cover/
54 |
55 | # Translations
56 | *.mo
57 | *.pot
58 |
59 | # Django stuff:
60 | *.log
61 | local_settings.py
62 | db.sqlite3
63 | db.sqlite3-journal
64 |
65 | # Flask stuff:
66 | instance/
67 | .webassets-cache
68 |
69 | # Scrapy stuff:
70 | .scrapy
71 |
72 | # Sphinx documentation
73 | docs/_build/
74 |
75 | # PyBuilder
76 | .pybuilder/
77 | target/
78 |
79 | # Jupyter Notebook
80 | .ipynb_checkpoints
81 |
82 | # IPython
83 | profile_default/
84 | ipython_config.py
85 |
86 | # pyenv
87 | # For a library or package, you might want to ignore these files since the code is
88 | # intended to run in multiple environments; otherwise, check them in:
89 | # .python-version
90 |
91 | # pipenv
92 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
93 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
94 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
95 | # install all needed dependencies.
96 | #Pipfile.lock
97 |
98 | # poetry
99 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
100 | # This is especially recommended for binary packages to ensure reproducibility, and is more
101 | # commonly ignored for libraries.
102 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
103 | #poetry.lock
104 |
105 | # pdm
106 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
107 | #pdm.lock
108 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
109 | # in version control.
110 | # https://pdm.fming.dev/#use-with-ide
111 | .pdm.toml
112 |
113 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
114 | __pypackages__/
115 |
116 | # Celery stuff
117 | celerybeat-schedule
118 | celerybeat.pid
119 |
120 | # SageMath parsed files
121 | *.sage.py
122 |
123 | # Environments
124 | .env
125 | .venv
126 | env/
127 | venv/
128 | ENV/
129 | env.bak/
130 | venv.bak/
131 |
132 | # Spyder project settings
133 | .spyderproject
134 | .spyproject
135 |
136 | # Rope project settings
137 | .ropeproject
138 |
139 | # mkdocs documentation
140 | /site
141 |
142 | # mypy
143 | .mypy_cache/
144 | .dmypy.json
145 | dmypy.json
146 |
147 | # Pyre type checker
148 | .pyre/
149 |
150 | # pytype static type analyzer
151 | .pytype/
152 |
153 | # Cython debug symbols
154 | cython_debug/
155 |
156 | # PyCharm
157 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
158 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
159 | # and can be added to the global gitignore or merged into this file. For a more nuclear
160 | # option (not recommended) you can uncomment the following to ignore the entire idea folder.
161 | #.idea/
162 |
--------------------------------------------------------------------------------
/e2e/e2e_utils.py:
--------------------------------------------------------------------------------
1 | import contextlib
2 | import logging
3 | import os
4 | import shlex
5 | import socket
6 | import subprocess
7 | import sys
8 | import time
9 | import typing
10 | from contextlib import closing
11 | from tempfile import TemporaryFile
12 |
13 | import requests
14 |
15 |
16 | LOGGER = logging.getLogger(__file__)
17 |
18 |
19 | def _find_free_port():
20 | """Find and return a free port on the local machine."""
21 | with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as s:
22 | s.bind(("", 0)) # 0 means that the OS chooses a random port
23 | s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
24 | return int(s.getsockname()[1]) # [1] contains the randomly selected port number
25 |
26 |
27 | class AsyncSubprocess:
28 | """A context manager. Wraps subprocess. Popen to capture output safely."""
29 |
30 | def __init__(self, args: typing.List[str], cwd: typing.Optional[str] = None,
31 | env: typing.Optional[typing.Dict[str, str]] = None):
32 | """Initialize an AsyncSubprocess instance.
33 |
34 | Args:
35 | args (List[str]): List of command-line arguments.
36 | cwd (str, optional): Current working directory. Defaults to None.
37 | env (dict, optional): Environment variables. Defaults to None.
38 | """
39 | self.args = args
40 | self.cwd = cwd
41 | self.env = env
42 | self._proc = None
43 | self._stdout_file = None
44 |
45 | def terminate(self) -> typing.Optional[str]:
46 | """Terminate the process and return its stdout/stderr in a string."""
47 | if self._proc is not None:
48 | self._proc.terminate()
49 | self._proc.wait()
50 | self._proc = None
51 |
52 | # Read the stdout file and close it
53 | stdout = None
54 | if self._stdout_file is not None:
55 | self._stdout_file.seek(0)
56 | stdout = self._stdout_file.read()
57 | self._stdout_file.close()
58 | self._stdout_file = None
59 |
60 | return stdout
61 |
62 | def __enter__(self) -> "AsyncSubprocess":
63 | """Start the subprocess when entering the context."""
64 | self.start()
65 | return self
66 |
67 | def __exit__(self, exc_type, exc_val, exc_tb):
68 | """Stop the subprocess and close resources when exiting the context."""
69 | self.stop()
70 |
71 | def start(self):
72 | # Start the process and capture its stdout/stderr output to a temp
73 | # file. We do this instead of using subprocess.PIPE (which causes the
74 | # Popen object to capture the output to its own internal buffer),
75 | # because large amounts of output can cause it to deadlock.
76 | self._stdout_file = TemporaryFile("w+")
77 | LOGGER.info("Running command: %s", shlex.join(self.args))
78 | self._proc = subprocess.Popen(
79 | self.args,
80 | cwd=self.cwd,
81 | stdout=self._stdout_file,
82 | stderr=subprocess.STDOUT,
83 | text=True,
84 | env={**os.environ.copy(), **self.env} if self.env else None,
85 | )
86 |
87 | def stop(self):
88 | """Terminate the subprocess and close resources."""
89 | if self._proc is not None:
90 | self._proc.terminate()
91 | self._proc = None
92 | if self._stdout_file is not None:
93 | self._stdout_file.close()
94 | self._stdout_file = None
95 |
96 |
97 | class StreamlitRunner:
98 | """A context manager for running Streamlit scripts."""
99 |
100 | def __init__(
101 | self, script_path: os.PathLike, server_port: typing.Optional[int] = None
102 | ):
103 | """Initialize a StreamlitRunner instance.
104 |
105 | Args:
106 | script_path (os.PathLike): Path to the Streamlit script to run.
107 | server_port (int, optional): Port for the Streamlit server. Defaults to None.
108 | """
109 | self._process = None
110 | self.server_port = server_port
111 | self.script_path = script_path
112 |
113 | def __enter__(self) -> "StreamlitRunner":
114 | """Start the Streamlit server when entering the context."""
115 | self.start()
116 | return self
117 |
118 | def __exit__(self, type, value, traceback):
119 | """Stop the Streamlit server and close resources when exiting the context."""
120 | self.stop()
121 |
122 | def start(self):
123 | """Start the Streamlit server using the specified script and options."""
124 | self.server_port = self.server_port or _find_free_port()
125 | self._process = AsyncSubprocess(
126 | [
127 | sys.executable,
128 | "-m",
129 | "streamlit",
130 | "run",
131 | str(self.script_path),
132 | f"--server.port={self.server_port}",
133 | "--server.headless=true",
134 | "--browser.gatherUsageStats=false",
135 | "--global.developmentMode=false",
136 | ]
137 | )
138 | self._process.start()
139 | if not self.is_server_running():
140 | self._process.stop()
141 | raise RuntimeError("Application failed to start")
142 |
143 | def stop(self):
144 | """Stop the Streamlit server and close resources."""
145 | self._process.stop()
146 |
147 | def is_server_running(self, timeout: int = 30) -> bool:
148 | """Check if the Streamlit server is running.
149 |
150 | Args:
151 | timeout (int, optional): Maximum time to wait for the server to start. Defaults to 30.
152 |
153 | Returns:
154 | bool: True if the server is running, False otherwise.
155 | """
156 | with requests.Session() as http_session:
157 | start_time = time.time()
158 | while True:
159 | with contextlib.suppress(requests.RequestException):
160 | response = http_session.get(self.server_url + "/_stcore/health")
161 | if response.text == "ok":
162 | return True
163 | time.sleep(3)
164 | if time.time() - start_time > 60 * timeout:
165 | return False
166 |
167 | @property
168 | def server_url(self) -> str:
169 | """Get the URL of the Streamlit server."""
170 | if not self.server_port:
171 | raise RuntimeError("Unknown server port")
172 | return f"http://localhost:{self.server_port}"
173 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | #######################################################################
2 | Terms and Conditions for the Highcharts for Python Toolkit License
3 | #######################################################################
4 |
5 | ***********************************************
6 | 1. Acceptance of the Terms and Conditions
7 | ***********************************************
8 |
9 | These terms and conditions (the “Wrapper T&Cs”) shall apply to Licensee’s license to and use of the Wrapper (as defined below), a complementary product to the Software owned and delivered by Highsoft AS (“Highsoft”). The Wrapper is owned by HCP LLC (“HCP”) and distributed by Highsoft AS or its partners, resellers or distributors, and these Wrapper T&Cs shall apply to the Licensee’s usage of the Wrapper irrespective of which license type(s) to the Software the Licensee has subscribed to or purchased from Highsoft.
10 |
11 | By installing or using the Wrapper or any part thereof, Licensee agrees to be bound by these Wrapper T&Cs, unless Licensee is using the Software and the Wrapper for evaluation purposes authorized by Highsoft, i.e. during a trial period.
12 |
13 | Without prejudice to the limited right to download and test the Wrapper for evaluation prior to the purchase of a Wrapper License, Licensee is not authorized to install or use the Wrapper unless Licensee pays the applicable Wrapper Fee and fully agrees to all terms and conditions set forth herein and to any terms set forth in related licensing agreements or terms from Highsoft to which these Wrapper T&Cs constitute an addendum.
14 |
15 | ***********************************************
16 | 2. Relationship to the Main Agreement
17 | ***********************************************
18 |
19 | A Wrapper License (as defined below) or renewal thereof can only be purchased by a Licensee either already holding a License to the Software, or at the same time as purchasing such a License, as an add-on product to such License, and only by a Licensee holding an active and valid enrolment in the Highcharts Advantage plan. The purchase of, and right to use the Licensed Software shall remain governed by the terms and conditions of the License issued by Highsoft to Licensee, whether this is Highsoft Standard License Agreement, Highsoft Terms and Conditions for Subscription to Annual License to Highsoft Software, or another licensing agreement as agreed between Highsoft and Licensee, as applicable (such terms and conditions between Highsoft and Licensee hereinafter collectively referred to as the “Main Agreement”).
20 |
21 | A Wrapper License (as defined below) can additionally be obtained by holding an active and valid Educational or Personal License to the Software. The right to use the Licensed Software shall remain governed by the terms and conditions of the Authorized User issued by Highsoft to Authorized User, as defined in the Educational and Personal License 1.0, (such terms and conditions between Highsoft and Authorized User hereinafter collectively referred to as the "Main Agreement").
22 |
23 | These Wrapper T&Cs constitute an addendum to the Main Agreement and an integral part thereof and apply to the use of the Wrapper only.
24 |
25 | In the case of any discrepancy between the Main Agreement and these Wrapper T&Cs related to the licensing of the Wrapper, these Wrapper T&Cs shall prevail.
26 |
27 | ***********************************************
28 | 3. Definitions
29 | ***********************************************
30 |
31 | Unless otherwise defined below or elsewhere in these Wrapper T&Cs, all capitalized terms used but not otherwise defined herein shall have the same definitions as set out in the Main Agreement.
32 |
33 | * **Agreement** shall mean the Main Agreement, these Wrapper T&Cs and the License Statement;
34 | * **Delivery Date** shall mean the date Licensee is invoiced by Highsoft for the Wrapper License, which will
35 | correspond to the Delivery Date for the Software if purchased together, or at a separate time, if purchased as an
36 | add-on to an existing License;
37 | * **HCP** shall mean HCP LLC, the company behind the Wrapper, a US limited liability corporation registered in the
38 | state of Delaware, with its registered office at 867 Boylston Street, 5th Floor #1674, Boston, MA 02116, United
39 | States.Licensee shall mean the legal entity to which the License (to the Licensed Software) and the Wrapper License
40 | has been granted, as expressly stated in the License Statement;
41 | * **Licensors** shall mean Highsoft and/or HCP collectively;
42 | * **Wrapper** shall mean the proprietary software products distributed by Highsoft and owned by HCP, including
43 | Highcharts for Python – the main product included in all licenses – in addition to Highcharts Stock for Python,
44 | Highcharts Maps for Python, Highcharts Gantt for Python, and other optional additional products or libraries which
45 | may be developed by HCP and offered within the Wrapper by Highsoft.
46 | * **Wrapper** Fee shall mean the fee payable by Licensee to Highsoft for the Wrapper License and for the right to
47 | receive the support services provided by HCP under these Wrapper T&Cs ;
48 | * **Wrapper License** shall mean the right to use the Wrapper in accordance with the Agreement and as set out herein,
49 | whether granted as a perpetual license or in the form of a time-limited subscription (subject to renewal), and as
50 | one or more of the license types as defined in the Main Agreement;
51 | * **Wrapper Term** shall have the meaning set out in section 7.
52 |
53 | ***********************************************
54 | 4. Ownership and Copyright
55 | ***********************************************
56 |
57 | The Wrapper is the property of HCP and is protected by copyright law as well as other statutory and non-statutory intellectual property law. All title and copyrights in and to the Wrapper are and shall remain owned fully and solely by HCP.
58 |
59 | Under the Agreement, the Wrapper is licensed to Licensee by Highsoft on behalf of HCP, not sold.
60 |
61 | The Licensors reserve all rights not expressly granted to Licensee under the Agreement. Without limiting the generality of the foregoing, Licensee acknowledges and agrees that: (a) except as specifically set forth herein, the Licensors retains all right, title and interest in and to the Wrapper and the Software, and Licensee does not acquire any right, title or interest to the Wrapper or the Software except as set forth herein; (b) any configuration or deployment of the Wrapper or the Software shall not affect or diminish the Licensor’s rights, title or interest in and to the Wrapper or the Software. The Agreement shall not limit in any way the Licensor’s right to develop, use, license, create derivative works of, or otherwise exploit the Wrapper or the Software, or to permit Third Parties to do so.
62 |
63 | The Licensors acknowledge and agree that (i) Licensee retains all rights, title and interest in and to any Licensee-owned software, and the Licensors do not acquire any right, title, or interest in or to such application; and (ii) any integration of the Wrapper with a Licensee-owned application shall not affect or diminish Licensee’s rights, title, and interest in and to such application.
64 |
65 | ***********************************************
66 | 5. Grant of License
67 | ***********************************************
68 |
69 | Subject to Licensee’s full payment of the Wrapper Fee, Highsoft grants to Licensee a right to use the Wrapper during the Wrapper Term (as defined in Section 7 below), on the same terms and conditions as set forth in the Main Agreement and the License Statement as modified by these Wrapper T&Cs. The Main Agreement shall apply accordingly to the Wrapper License insofar as the terms and conditions therein are applicable, whereas all references to the “Licensed Software” in the Main Agreement shall for the purposes of these Wrapper T&Cs to the extent applicable be deemed to include the Wrapper.
70 |
71 | The License type(s) chosen by Licensee for the Licensed Software shall apply to the Wrapper License, and depending on the purchased License type(s), as stated in the Licensed Statement, the relevant section in the Main Agreement detailing the usage rights and limitations for the License shall apply similarly to the Wrapper License.
72 |
73 | The rights granted to Licensee under the Agreement, is strictly limited to the usage rights granted under the chosen License type and with the scope as stated in the License Statement. The number of authorized Developers included in the Wrapper License is the same as the number of authorized Developers included in the License, as set out in the License Statement. The Wrapper may only be used in such Web Application(s), SaaS Application(s) and/or Licensee Product(s) as expressly identified in the License Statement.
74 |
75 | A Wrapper License shall include the components of the Wrapper which correspond to the Licensed Software to which Licensee holds a valid License, i.e. a Wrapper License purchased for Highcharts Stock shall include the Highcharts for Python software and all component libraries needed to implement the Highcharts Stock for Python library, and a Wrapper License purchased for Highcharts Maps shall include the Highcharts for Python software and all component libraries needed to implement the Highcharts Maps for Python, etc.
76 |
77 | 5.1 General Grants and Limitations
78 | ========================================
79 |
80 | The Wrapper License includes the support services provided by HCP set forth in Section 6 below.
81 | Irrespective of the chosen License type, the Wrapper License and any support services for the Wrapper shall be subject to renewal, and contingent upon Licensee’s continued enrollment in the Highcharts Advantage plan, as set out in section 7.
82 |
83 | Licensee may obtain the Wrapper source code by downloading the source code from the Highsoft Website or from Github or equivalent source code repository made available by HCP, by downloading and installing the source code from a public repository such as PyPi, and make its own edits, and keep its own repositories with the modified source code; provided, however, any such modifications shall be at Licensee’s own risk and shall void any support obligation of HCP hereunder.
84 |
85 | Licensee shall not modify, delete or obscure any notices of proprietary rights or any Wrapper identification or restrictions on or in the Wrapper found in the source code.
86 |
87 | ***********************************************
88 | 6. Wrapper Support
89 | ***********************************************
90 |
91 | A Wrapper License entitles Licensee to the support services and access to new Releases of the Wrapper as set out herein. While support for the Wrapper is contingent upon Licensee’s valid enrollment in the Highcharts Advantage plan and the annual number of hours of support available for support of the Licensed Software for each successive twelve month term during the period that Licensee is enrolled in Highcharts Advantage (each, a “Support Year”) are inclusive of the number of hours of support offered during such Support Year for the Wrapper, support of the Wrapper is not covered under Licensee’s enrollment in Highcharts Advantage, but is offered by Highsoft for a separate fee and provided separately, directly and independently by HCP. In the event that the Licensee is not enrolled in the Highcharts Advantage Plan, for example if the Main Agreement grants the Licensee a Personal or Educational License, then the Licensee shall not be entitled to the support services outlined herein.
92 |
93 | All support inquiries related to the Wrapper shall be sent to support@highchartspython.com or filed at https://www.highchartspython.com.
94 |
95 | Under a valid and effective Wrapper License, Licensee shall be entitled to receive from HCP:
96 |
97 | i. All new releases or updates of the Wrapper released during the applicable Advantage Period;
98 |
99 | Under a valid and effective Wrapper License, contingent upon the Licensee’s valid enrollment in the Highcharts Advantage Plan and payment of applicable fees, Licensee shall be entitled to receive from HCP:
100 |
101 | ii. Up to ten (10) hours of the personalized technical support for the Wrapper and/or the Licensed Software
102 | (combined) per Developer per Support Year based on the number of Developers stated in the License Statement for
103 | the License. Licensee may freely distribute its included total of ten (10) hours of personalized technical
104 | support between support of the Wrapper and support of the Licensed Software;
105 | iii. Technical support by e-mail;
106 | iv. Priority response;
107 | v. Access to 2nd line support for the Wrapper by core developers;
108 | vi. Online text chat with 1st line support Wrapper engineers;
109 | vii. Investigation of any claimed bug/error/malfunction/nonfunctioning of the Wrapper, and when possible,
110 | suggestions as to corrective or work-around solutions to the problems;
111 | viii. Supply of emergency hot fixes to the Wrapper;
112 | ix. Guidance and advice on implementing the Wrapper with the Software, as well as with any third-party systems and
113 | platforms to the extent such implementation is authorized by Highsoft. The guidance and advice shall include
114 | advice on best practices, limited code review, and guidance on parts of the code that are directly related to
115 | using the Wrapper with the Software;
116 | x. Any bug and error fixing, malfunctioning of the Wrapper is to be delivered outside the personalized technical
117 | support hours.
118 |
119 | Licensee is responsible for downloading and installing major version releases and updates of the Wrapper during the applicable Advantage Period. During each Advantage Period in which Licensee is validly enrolled in Highcharts Advantage, HCP will provide support for the current version and last major version releases of the Wrapper. For the avoidance of doubt, HCP shall have no obligation to provide support for any version of the Wrapper released prior to the major version release which immediately preceded the then current major version release of the Wrapper.
120 |
121 | The support services as set forth in this section (i) do not cover issues arising in connection with implementation of the Wrapper or Licensed Software in/to Licensee Products or Licensee’s own applications or to the Wrapper as modified by Licensee, and (ii) shall not extend to any Third Parties to which Licensee distributes Licensee Products, SaaS Application(s) or Web Application(s) containing the Wrapper, Licensed Software or any part thereof. Support to any Licensee customers shall hence be Licensee’s full and sole responsibility. The Licensors may, at its sole discretion, at any time choose to discontinue the supply of new Releases of the Wrapper.
122 |
123 | ***********************************************
124 | 7. Term and Renewal
125 | ***********************************************
126 |
127 | The term of the Wrapper License (the ”Wrapper Term”) shall correspond to the Initial Term of the License as indicated in the License Statement issued by Highsoft, and the provisions of the Main Agreement pertaining to the terms and conditions on the Term and Renewal of the License shall apply similarly to the Wrapper License; provided, however, in all cases, the support services set forth in Section 6 above are co-terminus with and contingent upon the Licensee’s enrollment in the Highsoft Advantage Plan as set forth in the applicable Main Agreement, including any renewal thereof, and payment of the corresponding Wrapper Fee.
128 |
129 | During the term of the Wrapper License, the Wrapper shall be made available by the Licensors and Licensee shall be authorized to download the Wrapper from the Highsoft Website, from Github or comparable source code repository maintained by HCP, or from a public Python library repository such as PyPi.
130 |
131 | ***********************************************
132 | 8. Termination
133 | ***********************************************
134 |
135 | The termination and expiration provisions in the Main Agreement shall apply similarly to the Agreement, and a termination or expiration of the Main Agreement, however occasioned, shall be construed as, and entail a termination of this Agreement.
136 |
137 | ***********************************************************
138 | 9. Annual License Fee, Renewal Fee, and Payment Terms
139 | ***********************************************************
140 |
141 | Licensee shall upon purchase of the Wrapper License pay the applicable Wrapper License Fee as determined by Highsoft, subject to the provisions on payment of the License Fee and Highcharts Advantage Fee as set out in the Main Agreement.
142 | For avoidance of doubt, the Wrapper License and accompanying rights including any subsequent renewals is granted to Licensee on the condition that all the due fees are paid to Highsoft in full and on time.
143 |
144 | ***********************************************************
145 | 10. Warranties and Representations
146 | ***********************************************************
147 |
148 | 10.1 Scope
149 | =================
150 |
151 | All warranties and representations given herein are provided by HCP, and HCP’s warranties and representations in this section 10 are limited to the Wrapper provided to Licensee under the Agreement.
152 |
153 | 10.2 HCP’s Warranties and Representations
154 | ===============================================
155 |
156 | HCP warrants and represents that:
157 |
158 | i. For a period of ninety (90) days following its Delivery Date, the Wrapper will perform substantially in
159 | accordance with HCP’s written specifications, provided that it has been used in accordance with all documentation
160 | and specifications made available on Highsoft's Website and not modified by Licensee,
161 | ii. HCP will perform its obligations under the Wrapper License and these Wrapper T&Cs in accordance with all
162 | applicable laws and regulations,
163 | iii. HCP has the full and unconditional ownership of the Wrapper,
164 | iv. The Wrapper does not infringe intellectual property rights of any Third Party,
165 | v. When installed in accordance with HCP’s written specifications, Third Party Dependencies, defined as software on
166 | which the Wrapper relies that has been developed and made available by Third Parties, shall be installed. HCP
167 | warrants that at the time of the Wrapper’s Release, such software was available for public distribution in
168 | accordance with its applicable licenses and its bundling with the Wrapper is fully compliant with the licenses of
169 | any and all such Third Party Dependencies. The Licensee can review the details of all such Third Party
170 | Dependencies, including their relevant licensing provisions, by reviewing the Wrapper documentation made
171 | available on Highsoft’s Website.
172 | vi. HCP has the requisite knowledge, personnel, resources and know-how to fully perform and deliver the Wrapper and
173 | associated services as stipulated by these Wrapper T&Cs in a professional manner,
174 | vii. HCP has not intentionally placed and will use its best efforts to avoid the placement of any Harmful Codes into
175 | the Wrapper provided under the Wrapper License. For the purpose of this section 10.2, "Harmful Codes" is
176 | defined as any program that infects, damages and/or impairs another program or data, disables hardware or
177 | software, or permits or assists in the breach of data.
178 |
179 | 10.3 Licensee’s Remedies
180 | =============================
181 |
182 | In the event of a breach, or alleged breach of any of the warranties in section 10.2, Licensee shall promptly notify either HCP or Highsoft and delete the Wrapper. Licensee’s sole remedy in such an event shall be that HCP shall re-supply or correct the Wrapper so that it operates according to the warranties set out in section 10.2. The warranties shall not apply if Licensee has modified, or used the Wrapper improperly, or on an operating environment not approved by HCP. Improper use and unapproved operating environments will be as set forth in the documentation provided to Licensee on or prior to Delivery Date.
183 |
184 | ***********************************************************
185 | 11. Limitation of Liability
186 | ***********************************************************
187 |
188 | 11.1 Highsoft
189 | ===================
190 |
191 | The Licensee understands and accepts that the Wrapper is provided by Highsoft as an Official Wrapper, and are hence not covered by the Warranties and Representations included in the Main Agreement, and is provided “as is” by Highsoft and may have errors and omissions. Highsoft disclaims any and all liability for the Wrapper or Licensee’s usage of or reliance on the Wrapper, and makes no warranties, express or implied, including but not limited to, warranties of merchantability, fitness for purpose, performance, accuracy, or non-infringing nature.
192 |
193 | 11.2 HCP
194 | ==================
195 |
196 | The Wrapper and all related support services supplied by HCP are provided ‘as is’ and may have errors and omissions. Thus, remedies are only available to Licensee in the event of any breach of the warranties set out in section 10.
197 |
198 | UNDER NO CIRCUMSTANCES, AND EVEN IF INFORMED THEREOF BY LICENSEE OR ANY OTHER PARTY, WILL THE LICENSORS BE LIABLE UNDER OR IN CONNECTION WITH A CLAIM RELATING TO THESE WRAPPER T&CS OR ITS SUBJECT MATTER FOR (i) LOSS OF, OR DAMAGE TO, DATA; (ii) SPECIAL, INCIDENTAL, CONSEQUENTIAL OR INDIRECT DAMAGES; OR (iii) LOST PROFITS, BUSINESS, REVENUE, GOODWILL, OR ANTICIPATED SAVINGS.
199 |
200 | Incorporation of the Wrapper into any application as further described in the Main Agreement shall not in any manner expand HCP’s liabilities under the Annual Wrapper License. Thus, HCP shall not under any circumstance be neither responsible nor liable for any aspects of such Licensee application(s), including but not limited to its reliability, uptime/downtime, functioning or fitness for purpose. Any obligations, liabilities or warranties undertaken by Licensee towards its customers with respect to such application(s) shall apply only between mentioned parties, and Licensee hereby undertakes to indemnify and hold the Licensors harmless from and against any and all losses, clams and damages related to such application(s).
201 |
202 | IN NO EVENT WILL THE LIABILITY OF THE LICENSORS UNDER OR IN CONNECTION WITH THESE WRAPPER T&CS OR ITS SUBJECT MATTER, UNDER ANY LEGAL OR EQUITABLE THEORY, INCLUDING BREACH OF CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY, AND OTHERWISE, EXCEED THE TOTAL WRAPPER FEE PAID BY THE LICENSEE DURING THE LAST TWELVE (12) MONTHS PRIOR TO OCCURRENCE GIVING CAUSE TO SUCH LIABILITY.
203 |
204 | ***********************************************************
205 | 12. Confidentiality
206 | ***********************************************************
207 |
208 | As a trusted partner of Highsoft, HCP is bound by the confidentiality provisions of the Main Agreement, which shall apply similarly for the Wrapper T&Cs, and Licensee hereby grants to Highsoft the explicit right to share Confidential Information with HCP as needed.
209 |
210 | ***********************************************************
211 | 13. Applicable Law and Venue
212 | ***********************************************************
213 |
214 | The construction, validity and operation of the Wrapper License and these Wrapper T&Cs, and the performance of all obligations hereunder, shall be governed by and construed in accordance with the laws of the Commonwealth of Massachusetts, United States of America, without regard to conflict of law principles that would result in the application of any law other than the law of the Commonwealth of Massachusetts.
215 |
216 | In the event of a dispute, controversy or claim between Licensee and HCP arising out of or relating to the Wrapper License and these Wrapper T&Cs, or the breach, termination, or invalidity thereof the Parties shall meet in an effort to resolve such dispute, controversy or claim amicably through negotiation. If the Parties do not reach an amicable solution within two (2) weeks of such efforts being initiated, either Party may initiate legal proceedings in the United States federal courts and state courts located in the Commonwealth of Massachusetts, which courts shall have sole and exclusive jurisdiction and venue to adjudicate any such dispute, controversy or claim. The Parties consent to the exclusive jurisdiction of the courts specified above, and expressly waive any objection to the jurisdiction or convenience of such courts.
217 |
218 | Any dispute, controversy or claim between Licensee and Highsoft, shall be resolved in accordance with the provisions of the Main Agreement.
219 |
220 | ***********************************************************
221 | 14. Processing of Personal Data
222 | ***********************************************************
223 |
224 | To the extent the purchase of the Wrapper License involves processing by either Highsoft and/or HCP of personal data about the Licensee or Licensee’s customers or personnel related to the purchase of the Wrapper License, Highsoft and HCP shall be acting as joint and independent data controllers. The provisions of the Main Agreement pertaining to Highsoft’s processing of personal data shall apply similarly to any processing by Highsoft of personal data in relation to the Agreement.
225 |
226 | To the extent the support services performed by HCP under this Agreement involves processing by HCP of personal data about the Licensee or Licensee’s customers or personnel, HCP shall be acting as a data controller.
227 |
228 | ***********************************************************
229 | 15. Miscellaneous
230 | ***********************************************************
231 |
232 | The provisions of the Main Agreement entitled “Miscellaneous” shall apply similarly to these Wrapper T&Cs, as applicable.
--------------------------------------------------------------------------------