浏览代码

Merge pull request #488 from Avaiga/feature/merge-templates

Merge taipy-templates
João André 1 年之前
父节点
当前提交
d7eafd66be
共有 66 个文件被更改,包括 1936 次插入1 次删除
  1. 1 1
      .github/workflows/linter.yml
  2. 10 0
      src/__init__.py
  3. 22 0
      src/taipy/templates/.editorconfig
  4. 23 0
      src/taipy/templates/.flake8
  5. 2 0
      src/taipy/templates/.gitattributes
  6. 29 0
      src/taipy/templates/.github/workflows/packaging.yml
  7. 90 0
      src/taipy/templates/.github/workflows/publish.yml
  8. 105 0
      src/taipy/templates/.github/workflows/release-dev.yml
  9. 69 0
      src/taipy/templates/.github/workflows/release.yml
  10. 36 0
      src/taipy/templates/.github/workflows/tests.yml
  11. 23 0
      src/taipy/templates/.gitignore
  12. 50 0
      src/taipy/templates/.pre-commit-config.yaml
  13. 128 0
      src/taipy/templates/CODE_OF_CONDUCT.md
  14. 135 0
      src/taipy/templates/CONTRIBUTING.md
  15. 202 0
      src/taipy/templates/LICENSE
  16. 1 0
      src/taipy/templates/MANIFEST.in
  17. 26 0
      src/taipy/templates/Pipfile
  18. 63 0
      src/taipy/templates/README.md
  19. 15 0
      src/taipy/templates/default/cookiecutter.json
  20. 180 0
      src/taipy/templates/default/hooks/post_gen_project.py
  21. 9 0
      src/taipy/templates/default/hooks/pre_gen_project.py
  22. 1 0
      src/taipy/templates/default/{{cookiecutter.__root_folder_name}}/algorithms/__init__.py
  23. 14 0
      src/taipy/templates/default/{{cookiecutter.__root_folder_name}}/algorithms/algorithms.py
  24. 1 0
      src/taipy/templates/default/{{cookiecutter.__root_folder_name}}/configuration/__init__.py
  25. 17 0
      src/taipy/templates/default/{{cookiecutter.__root_folder_name}}/configuration/config.py
  26. 1 0
      src/taipy/templates/default/{{cookiecutter.__root_folder_name}}/pages/__init__.py
  27. 1 0
      src/taipy/templates/default/{{cookiecutter.__root_folder_name}}/pages/page_example/page_example.md
  28. 10 0
      src/taipy/templates/default/{{cookiecutter.__root_folder_name}}/pages/page_example/page_example.py
  29. 3 0
      src/taipy/templates/default/{{cookiecutter.__root_folder_name}}/pages/root.md
  30. 10 0
      src/taipy/templates/default/{{cookiecutter.__root_folder_name}}/pages/root.py
  31. 0 0
      src/taipy/templates/default/{{cookiecutter.__root_folder_name}}/requirements.txt
  32. 1 0
      src/taipy/templates/default/{{cookiecutter.__root_folder_name}}/sections/import.txt
  33. 1 0
      src/taipy/templates/default/{{cookiecutter.__root_folder_name}}/sections/main.txt
  34. 0 0
      src/taipy/templates/default/{{cookiecutter.__root_folder_name}}/sections/page_content.txt
  35. 0 0
      src/taipy/templates/default/{{cookiecutter.__root_folder_name}}/{{cookiecutter.__main_file}}.py
  36. 2 0
      src/taipy/templates/mypy.ini
  37. 4 0
      src/taipy/templates/pyproject.toml
  38. 11 0
      src/taipy/templates/scenario-management/cookiecutter.json
  39. 38 0
      src/taipy/templates/scenario-management/hooks/post_gen_project.py
  40. 2 0
      src/taipy/templates/scenario-management/{{cookiecutter.__root_folder_name}}/.gitignore
  41. 0 0
      src/taipy/templates/scenario-management/{{cookiecutter.__root_folder_name}}/.taipyignore
  42. 1 0
      src/taipy/templates/scenario-management/{{cookiecutter.__root_folder_name}}/algos/__init__.py
  43. 3 0
      src/taipy/templates/scenario-management/{{cookiecutter.__root_folder_name}}/algos/algos.py
  44. 0 0
      src/taipy/templates/scenario-management/{{cookiecutter.__root_folder_name}}/config/__init__.py
  45. 25 0
      src/taipy/templates/scenario-management/{{cookiecutter.__root_folder_name}}/config/config.py
  46. 24 0
      src/taipy/templates/scenario-management/{{cookiecutter.__root_folder_name}}/config/config.toml
  47. 6 0
      src/taipy/templates/scenario-management/{{cookiecutter.__root_folder_name}}/config/config_with_toml.py
  48. 2 0
      src/taipy/templates/scenario-management/{{cookiecutter.__root_folder_name}}/pages/__init__.py
  49. 1 0
      src/taipy/templates/scenario-management/{{cookiecutter.__root_folder_name}}/pages/job_page/__init__.py
  50. 1 0
      src/taipy/templates/scenario-management/{{cookiecutter.__root_folder_name}}/pages/job_page/job_page.md
  51. 3 0
      src/taipy/templates/scenario-management/{{cookiecutter.__root_folder_name}}/pages/job_page/job_page.py
  52. 21 0
      src/taipy/templates/scenario-management/{{cookiecutter.__root_folder_name}}/pages/root.md
  53. 7 0
      src/taipy/templates/scenario-management/{{cookiecutter.__root_folder_name}}/pages/root.py
  54. 1 0
      src/taipy/templates/scenario-management/{{cookiecutter.__root_folder_name}}/pages/scenario_page/__init__.py
  55. 39 0
      src/taipy/templates/scenario-management/{{cookiecutter.__root_folder_name}}/pages/scenario_page/data_node_management.py
  56. 12 0
      src/taipy/templates/scenario-management/{{cookiecutter.__root_folder_name}}/pages/scenario_page/scenario_page.md
  57. 19 0
      src/taipy/templates/scenario-management/{{cookiecutter.__root_folder_name}}/pages/scenario_page/scenario_page.py
  58. 1 0
      src/taipy/templates/scenario-management/{{cookiecutter.__root_folder_name}}/requirements.txt
  59. 43 0
      src/taipy/templates/scenario-management/{{cookiecutter.__root_folder_name}}/{{cookiecutter.__main_file}}.py
  60. 57 0
      src/taipy/templates/setup.py
  61. 10 0
      src/taipy/templates/tests/__init__.py
  62. 195 0
      src/taipy/templates/tests/test_default_template.py
  63. 92 0
      src/taipy/templates/tests/test_scenario_mgt_template.py
  64. 18 0
      src/taipy/templates/tests/utils.py
  65. 18 0
      src/taipy/templates/tox.ini
  66. 1 0
      src/taipy/templates/version.json

+ 1 - 1
.github/workflows/linter.yml

@@ -21,5 +21,5 @@ jobs:
           use-pylint: false
           extra-black-options: "--line-length=120"
           extra-pycodestyle-options: "--max-line-length=120 --ignore=E121,E123,E126,E226,E24,E704,W503,W504,E203"
-          extra-mypy-options: "--ignore-missing-imports --implicit-optional --no-namespace-packages"
+          extra-mypy-options: "--ignore-missing-imports --implicit-optional --no-namespace-packages --exclude src/taipy/templates --follow-imports skip"
           extra-isort-options: "--line-length=120 --force-grid-wrap=10 --multi-line=VERTICAL_HANGING_INDENT --trailing-comma"

+ 10 - 0
src/__init__.py

@@ -0,0 +1,10 @@
+# Copyright 2023 Avaiga Private Limited
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+#        http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+# an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations under the License.

+ 22 - 0
src/taipy/templates/.editorconfig

@@ -0,0 +1,22 @@
+# http://editorconfig.org
+
+root = true
+
+[*]
+indent_style = space
+indent_size = 4
+max_line_length = 120
+trim_trailing_whitespace = true
+insert_final_newline = true
+charset = utf-8
+end_of_line = lf
+
+[*.bat]
+indent_style = tab
+end_of_line = crlf
+
+[LICENSE]
+insert_final_newline = false
+
+[Makefile]
+indent_style = tab

+ 23 - 0
src/taipy/templates/.flake8

@@ -0,0 +1,23 @@
+[flake8]
+# required by black, https://github.com/psf/black/blob/master/.flake8
+max-line-length = 120
+max-complexity = 18
+ignore = E203, E266, E501, E722, W503, F403, F401
+select = B,C,E,F,W,T4,B9
+docstring-convention = google
+per-file-ignores =
+    __init__.py:F401
+exclude =
+    .git,
+    __pycache__,
+    setup.py,
+    build,
+    dist,
+    releases,
+    .venv,
+    .tox,
+    .mypy_cache,
+    .pytest_cache,
+    .vscode,
+    .github,
+    tests

+ 2 - 0
src/taipy/templates/.gitattributes

@@ -0,0 +1,2 @@
+# Set the default behavior, in case people don't have core.autocrlf set.
+* text=auto

+ 29 - 0
src/taipy/templates/.github/workflows/packaging.yml

@@ -0,0 +1,29 @@
+name: Test package installation
+
+on:
+  push:
+    branches: [ develop ]
+  pull_request:
+    branches: [ develop ]
+  schedule:
+    - cron: "0 8 * * *"
+
+jobs:
+  standard-packages:
+    timeout-minutes: 10
+    strategy:
+      matrix:
+        python-versions: [ '3.8', '3.9', '3.10', '3.11' ]
+        os: [ ubuntu-latest, windows-latest, macos-latest ]
+
+    runs-on: ${{ matrix.os }}
+
+    steps:
+      - uses: actions/checkout@v2
+      - uses: actions/setup-python@v2
+        with:
+          python-version: ${{ matrix.python-versions }}
+
+      - name: Test packaging
+        run: |
+          pip install .

+ 90 - 0
src/taipy/templates/.github/workflows/publish.yml

@@ -0,0 +1,90 @@
+name: Publish on Pypi
+
+on:
+  workflow_dispatch:
+    inputs:
+      version:
+        description: "The tag of the package to publish on Pypi (ex: 1.0.0, 1.0.0.dev0)"
+        required: true
+
+jobs:
+  test-package:
+    timeout-minutes: 40
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v3
+      - uses: actions/setup-python@v2
+        with:
+          python-version: 3.8
+
+      - name: Extract Github Tag Version
+        id: vars
+        run: echo "tag=${GITHUB_REF#refs/*/}" >> $GITHUB_OUTPUT
+
+      - name: Ensure package version is properly set
+        run: |
+          echo """
+          import json, sys, os
+          with open(f\"src{os.sep}taipy{os.sep}templates{os.sep}version.json\") as version_file:
+            version_o = json.load(version_file)
+          version = f'{version_o.get(\"major\")}.{version_o.get(\"minor\")}.{version_o.get(\"patch\")}'
+          if vext := version_o.get(\"ext\"):
+            version = f'{version}.{vext}'
+          if version != sys.argv[1]:
+            raise ValueError(f\"Invalid version {version} / {sys.argv[1]}\")
+          if sys.argv[1] != sys.argv[2]:
+            raise ValueError(f\"Invalid tag version {sys.argv[2]} with package version {sys.argv[1]}\")
+          """ > /tmp/check.py
+          python /tmp/check.py "${{ github.event.inputs.version }}" "${{ steps.vars.outputs.tag }}"
+
+      - name: Download assets from github release tag
+        run: |
+          gh release download ${{ github.event.inputs.version }} --dir dist
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+      - name: Verify there is a release asset
+        run: |
+          if [ ! -f dist/${{ github.event.repository.name }}-${{ github.event.inputs.version }}.tar.gz ]; then
+            echo "No release asset found"
+            exit 1
+          fi
+
+  publish-to-pypi:
+    needs: [test-package]
+    timeout-minutes: 20
+    environment: publish
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v3
+
+      - name: Download assets from tag
+        run: |
+          gh release download ${{ github.event.inputs.version }} --dir dist
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+      - name: Publish to PyPI
+        uses: pypa/gh-action-pypi-publish@release/v1
+        with:
+          user: __token__
+          password: ${{ secrets.PYPI_API_TOKEN }}
+
+  test-published-package:
+    needs: [publish-to-pypi]
+    timeout-minutes: 30
+    strategy:
+      matrix:
+        python-versions: ['3.8','3.9','3.10', '3.11']
+        os: [ubuntu-latest,windows-latest,macos-latest]
+    runs-on: ${{ matrix.os }}
+    steps:
+      - uses: actions/checkout@v3
+      - uses: actions/setup-python@v2
+        with:
+          python-version: ${{ matrix.python-versions }}
+
+      - name: Install package
+        run: |
+          pip install --upgrade pip
+          pip install --no-cache-dir ${{ github.event.repository.name }}==${{ github.event.inputs.version }}

+ 105 - 0
src/taipy/templates/.github/workflows/release-dev.yml

@@ -0,0 +1,105 @@
+name: Create Github Dev Release
+
+on:
+  workflow_dispatch:
+
+jobs:
+  release-dev-package:
+    timeout-minutes: 40
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v3
+        with:
+          ssh-key: ${{secrets.DEPLOY_KEY}}
+
+      - uses: actions/setup-python@v4
+        with:
+          python-version: 3.8
+
+      - name: Ensure package version has 'dev' suffix
+        run: |
+          echo """
+          import json, sys, os
+          SUFFIX = 'dev'
+          with open(f\"src{os.sep}taipy{os.sep}templates{os.sep}version.json\") as version_file:
+              version_o = json.load(version_file)
+          version = f'{version_o.get(\"major\")}.{version_o.get(\"minor\")}.{version_o.get(\"patch\")}'
+          if vext := version_o.get(\"ext\"):
+              version = f'{version}.{vext}'
+          if SUFFIX not in version:
+              raise ValueError(f\"version {version} does not contain suffix {SUFFIX}\")
+          """ > /tmp/check1.py
+          python /tmp/check1.py
+
+      - name: Extract package version
+        id: current-version
+        run: |
+          echo """
+          import json, os
+          with open(f\"src{os.sep}taipy{os.sep}templates{os.sep}version.json\") as version_file:
+              version_o = json.load(version_file)
+          version = f'{version_o.get(\"major\")}.{version_o.get(\"minor\")}.{version_o.get(\"patch\")}'
+          if vext := version_o.get(\"ext\"):
+              version = f'{version}.{vext}'
+          print(f'VERSION={version}')
+          """ > /tmp/check2.py
+          python /tmp/check2.py >> $GITHUB_OUTPUT
+
+      - name: Install dependencies
+        run: |
+          python -m pip install --upgrade pip
+          pip install build
+
+      - name: Build package
+        run: python setup.py build_py && python -m build
+
+      - name: Install the package and test it
+        run: |
+          # Install package
+          echo "Installing package..."
+          pip install ./dist/${{ github.event.repository.name }}-${{ steps.current-version.outputs.VERSION }}.tar.gz
+
+      - name: Extract commit hash
+        shell: bash
+        run: echo "##[set-output name=hash;]$(echo $(git rev-parse HEAD))"
+        id: extract_hash
+
+      - name: Create/update release and tag
+        run: |
+          echo "Creating release ${{ steps.current-version.outputs.VERSION }}"
+          gh release create ${{ steps.current-version.outputs.VERSION }} ./dist/${{ github.event.repository.name }}-${{ steps.current-version.outputs.VERSION }}.tar.gz --target ${{ steps.extract_hash.outputs.hash }} --prerelease --title ${{ steps.current-version.outputs.VERSION }} --notes "Dev Release ${{ steps.current-version.outputs.VERSION }}"
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+      - name: Reset changes
+        run: |
+          git reset --hard HEAD
+          git clean -fdx
+
+      - name: Increase dev version
+        id: new-version
+        run: |
+          echo """
+          import json, os
+          with open(f'src{os.sep}taipy{os.sep}templates{os.sep}version.json') as version_file:
+              version_o = json.load(version_file)
+              if version_o is None or 'dev' not in version_o['ext']:
+                  raise ValueError('Invalid version file. Version must contain dev suffix.')
+              prev_version = version_o['ext']
+              new_version = 'dev' + str(int(version_o['ext'].replace('dev', '')) + 1)
+              with open(f'src{os.sep}taipy{os.sep}templates{os.sep}version.json') as r:
+                  text = r.read().replace(prev_version, new_version)
+              with open(f'src{os.sep}taipy{os.sep}templates{os.sep}version.json', mode='w') as w:
+                  w.write(text)
+              with open(f\"src{os.sep}taipy{os.sep}templates{os.sep}version.json\") as version_file:
+                  version_o = json.load(version_file)
+              version = f'{version_o.get(\"major\")}.{version_o.get(\"minor\")}.{version_o.get(\"patch\")}'
+              if vext := version_o.get(\"ext\"):
+                  version = f'{version}.{vext}'
+              print(f'VERSION={version}')
+          """ > /tmp/increase_dev_version.py
+          python /tmp/increase_dev_version.py >> $GITHUB_OUTPUT
+
+      - uses: stefanzweifel/git-auto-commit-action@v4
+        with:
+          commit_message: Update version to ${{ steps.new-version.outputs.VERSION }}

+ 69 - 0
src/taipy/templates/.github/workflows/release.yml

@@ -0,0 +1,69 @@
+name: Create Github Release
+
+on:
+  workflow_dispatch:
+    inputs:
+      version:
+        description: "The release/package version to create (ex: 1.0.0)"
+        required: true
+
+jobs:
+  release-package:
+    timeout-minutes: 40
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v3
+      - uses: actions/setup-python@v2
+        with:
+          python-version: 3.8
+
+      - name: Extract branch name
+        shell: bash
+        run: echo "##[set-output name=branch;]$(echo ${GITHUB_REF#refs/heads/})"
+        id: extract_branch
+
+      - name: Ensure package version is properly set
+        run: |
+          echo """
+          import json, sys, os
+          with open(f\"src{os.sep}taipy{os.sep}templates{os.sep}version.json\") as version_file:
+              version_o = json.load(version_file)
+          version = f'{version_o.get(\"major\")}.{version_o.get(\"minor\")}.{version_o.get(\"patch\")}'
+          if vext := version_o.get(\"ext\"):
+              version = f'{version}.{vext}'
+          if version != sys.argv[1]:
+              raise ValueError(f\"Invalid version {version} / {sys.argv[1]}\")
+          """ > /tmp/check1.py
+          python /tmp/check1.py "${{ github.event.inputs.version }}"
+
+      - name: Validate branch name
+        run: |
+          echo """
+          import json, sys, os
+          with open(f\"src{os.sep}taipy{os.sep}templates{os.sep}version.json\") as version_file:
+              version = json.load(version_file)
+          if f'release/{version.get(\"major\")}.{version.get(\"minor\")}' != sys.argv[1]:
+              raise ValueError(f'Branch name mismatch: release/{version.get(\"major\")}.{version.get(\"minor\")} != {sys.argv[1]}')
+          """ > /tmp/check2.py
+          python /tmp/check2.py "${{ steps.extract_branch.outputs.branch }}"
+
+      - name: Install dependencies
+        run: |
+          python -m pip install --upgrade pip
+          pip install build
+
+      - name: Build and install package
+        run: |
+          python setup.py build_py && python -m build
+          pip install dist/*.tar.gz
+
+      - name: Extract commit hash
+        shell: bash
+        run: echo "##[set-output name=hash;]$(echo $(git rev-parse HEAD))"
+        id: extract_hash
+
+      - name: Create/update release and tag
+        run: |
+            gh release create ${{ github.event.inputs.version }} ./dist/${{ github.event.repository.name }}-${{ github.event.inputs.version }}.tar.gz --target ${{ steps.extract_hash.outputs.hash }} --title ${{ github.event.inputs.version }}
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

+ 36 - 0
src/taipy/templates/.github/workflows/tests.yml

@@ -0,0 +1,36 @@
+name: Python tests
+
+on:
+  push:
+    branches: [ develop ]
+  pull_request:
+    branches: [ develop ]
+
+jobs:
+  backend:
+    timeout-minutes: 40
+    strategy:
+      matrix:
+        python-versions: ['3.8', '3.9', '3.10', '3.11']
+        os: [ubuntu-latest, windows-latest, macos-latest]
+      fail-fast: false
+    runs-on: ${{ matrix.os }}
+
+    steps:
+      - uses: actions/checkout@v3
+      - uses: actions/setup-python@v4
+        with:
+          python-version: ${{ matrix.python-versions }}
+
+      - name: Tests
+        run: |
+          pip install tox
+          tox -e tests
+
+      - name: Notify user if failed
+        if: failure() && github.event_name == 'workflow_dispatch'
+        run: |
+          if [[ -n "${{ github.event.inputs.user-to-notify }}" ]]; then
+            curl "${{ secrets.notify_endpoint }}" -d '{"username": "${{ github.event.inputs.user-to-notify }}", "url": "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" }' -H "Content-Type: application/json"
+          fi
+        shell: bash

+ 23 - 0
src/taipy/templates/.gitignore

@@ -0,0 +1,23 @@
+.idea
+.venv
+__pycache__
+
+# Distribution / packaging
+Pipfile.lock
+build/
+*.egg-info/
+# mypy
+.mypy_cache/
+dist/
+
+# IDE settings
+.vscode/
+.idea/
+.idea/taipy.iml
+.DS_Store
+
+# Core .data directory
+.data/
+
+# demo files
+demo-*

+ 50 - 0
src/taipy/templates/.pre-commit-config.yaml

@@ -0,0 +1,50 @@
+repos:
+-   repo: https://github.com/pre-commit/mirrors-mypy
+    rev: 'v0.991'  # Use the sha / tag you want to point at
+    hooks:
+    -   id: mypy
+        additional_dependencies: [
+                'types-Markdown',
+                'types-python-dateutil',
+                'types-pytz',
+                'types-tzlocal',
+        ]
+        args:
+        - --ignore-missing-imports
+        - --implicit-optional
+        - --no-namespace-packages
+-   repo: https://github.com/Lucas-C/pre-commit-hooks
+    rev: v1.1.10
+    hooks:
+    -   id: forbid-crlf
+    -   id: remove-crlf
+    -   id: forbid-tabs
+    -   id: remove-tabs
+-   repo: https://github.com/pre-commit/pre-commit-hooks
+    rev: v4.0.1
+    hooks:
+    - id: trailing-whitespace
+    - id: end-of-file-fixer
+    - id: check-merge-conflict
+    - id: check-yaml
+      args: [--unsafe]
+-   repo: https://github.com/pre-commit/mirrors-isort
+    rev: v5.9.3
+    hooks:
+    - id: isort
+      args:
+      - --line-length=120
+      - --force-grid-wrap=10
+      - --multi-line=VERTICAL_HANGING_INDENT
+      - --trailing-comma
+-   repo: https://github.com/ambv/black
+    rev: 22.3.0
+    hooks:
+    - id: black
+      args: [--line-length=120]
+      language_version: python3
+-   repo: https://github.com/pycqa/flake8
+    rev: 3.9.2
+    hooks:
+    -   id: flake8
+        additional_dependencies: [flake8-typing-imports==1.10.0]

+ 128 - 0
src/taipy/templates/CODE_OF_CONDUCT.md

@@ -0,0 +1,128 @@
+# Contributor Covenant Code of Conduct
+
+## Our Pledge
+
+We as members, contributors, and leaders pledge to make participation in our
+community a harassment-free experience for everyone, regardless of age, body
+size, visible or invisible disability, ethnicity, sex characteristics, gender
+identity and expression, level of experience, education, socio-economic status,
+nationality, personal appearance, race, religion, or sexual identity
+and orientation.
+
+We pledge to act and interact in ways that contribute to an open, welcoming,
+diverse, inclusive, and healthy community.
+
+## Our Standards
+
+Examples of behavior that contributes to a positive environment for our
+community include:
+
+* Demonstrating empathy and kindness toward other people.
+* Being respectful of differing opinions, viewpoints, and experiences.
+* Giving and gracefully accepting constructive feedback.
+* Accepting responsibility and apologizing to those affected by our mistakes,
+  and learning from the experience.
+* Focusing on what is best not just for us as individuals, but for the
+  overall community.
+
+Examples of unacceptable behavior include:
+
+* The use of sexualized language or imagery, and sexual attention or
+  advances of any kind.
+* Trolling, insulting or derogatory comments, and personal or political attacks
+* Public or private harassment.
+* Publishing others' private information, such as a physical or email
+  address, without their explicit permission.
+* Other conduct which could reasonably be considered inappropriate in a
+  professional setting.
+
+## Enforcement Responsibilities
+
+Community leaders are responsible for clarifying and enforcing our standards of
+acceptable behavior and will take appropriate and fair corrective action in
+response to any behavior that they deem inappropriate, threatening, offensive,
+or harmful.
+
+Community leaders have the right and responsibility to remove, edit, or reject
+comments, commits, code, wiki edits, issues, and other contributions that are
+not aligned to this Code of Conduct, and will communicate reasons for moderation
+decisions when appropriate.
+
+## Scope
+
+This Code of Conduct applies within all community spaces, and also applies when
+an individual is officially representing the community in public spaces.
+Examples of representing our community include using an official e-mail address,
+posting via an official social media account, or acting as an appointed
+representative at an online or offline event.
+
+## Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be
+reported to the community leaders responsible for enforcement at
+rnd@avaiga.com.
+All complaints will be reviewed and investigated promptly and fairly.
+
+All community leaders are obligated to respect the privacy and security of the
+reporter of any incident.
+
+## Enforcement Guidelines
+
+Community leaders will follow these Community Impact Guidelines in determining
+the consequences for any action they deem in violation of this Code of Conduct:
+
+### 1. Correction
+
+**Community Impact**: Use of inappropriate language or other behavior deemed
+unprofessional or unwelcome in the community.
+
+**Consequence**: A private, written warning from community leaders, providing
+clarity around the nature of the violation and an explanation of why the
+behavior was inappropriate. A public apology may be requested.
+
+### 2. Warning
+
+**Community Impact**: A violation through a single incident or series
+of actions.
+
+**Consequence**: A warning with consequences for continued behavior. No
+interaction with the people involved, including unsolicited interaction with
+those enforcing the Code of Conduct, for a specified period of time. This
+includes avoiding interactions in community spaces as well as external channels
+like social media. Violating these terms may lead to a temporary or
+permanent ban.
+
+### 3. Temporary Ban
+
+**Community Impact**: A serious violation of community standards, including
+sustained inappropriate behavior.
+
+**Consequence**: A temporary ban from any sort of interaction or public
+communication with the community for a specified period of time. No public or
+private interaction with the people involved, including unsolicited interaction
+with those enforcing the Code of Conduct, is allowed during this period.
+Violating these terms may lead to a permanent ban.
+
+### 4. Permanent Ban
+
+**Community Impact**: Demonstrating a pattern of violation of community
+standards, including sustained inappropriate behavior,  harassment of an
+individual, or aggression toward or disparagement of classes of individuals.
+
+**Consequence**: A permanent ban from any sort of public interaction within
+the community.
+
+## Attribution
+
+This Code of Conduct is adapted from the [Contributor Covenant][homepage],
+version 2.0, available at
+https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
+
+Community Impact Guidelines were inspired by [Mozilla's code of conduct
+enforcement ladder](https://github.com/mozilla/diversity).
+
+[homepage]: https://www.contributor-covenant.org
+
+For answers to common questions about this code of conduct, see the FAQ at
+https://www.contributor-covenant.org/faq. Translations are available at
+https://www.contributor-covenant.org/translations.

+ 135 - 0
src/taipy/templates/CONTRIBUTING.md

@@ -0,0 +1,135 @@
+# Contributions
+
+Thanks for your interest in helping improve Taipy! Contributions are welcome, and they are greatly appreciated!
+Every little help and credit will always be given.
+
+There are multiple ways to contribute to Taipy: code, but also reporting bugs, creating feature requests, helping
+other users in our forums, [stack**overflow**](https://stackoverflow.com/), etc.
+
+Today the only way to communicate with the Taipy team is by GitHub issues.
+
+## Never contributed on an open source project before ?
+
+Have a look on this [GitHub documentation](https://docs.github.com/en/get-started/quickstart/contributing-to-projects).
+
+## Report bugs
+
+Reporting bugs is through [GitHub issues](https://github.com/Avaiga/taipy/issues).
+
+Please report relevant information and preferably code that exhibits the problem. We provide templates to help you
+describe the issue.
+
+The Taipy team will analyse and try to reproduce the bug to provide feedback. If confirmed, we will add a priority
+to the issue and add it in our backlog. Feel free to propose a pull request to fix it.
+
+## Issue reporting, feedback, proposal, design or any other comment
+
+Any feedback or proposal is greatly appreciated! Do not hesitate to create an issue with the appropriate template on
+[GitHub](https://github.com/Avaiga/taipy/issues).
+
+The Taipy team will analyse your issue and return to you as soon as possible.
+
+## Improve Documentation
+
+Do not hesitate to create an issue or pull request directly on the
+[taipy-doc repository](https://github.com/Avaiga/taipy-doc).
+
+## Implement Features
+
+The Taipy team manages its backlog in private. Each issue that will be done during our current sprint is
+attached to the `current sprint`. Please, do not work on it, the Taipy team is on it.
+
+## Code organisation
+
+Taipy is organised in five main repositories:
+
+- [taipy-config](https://github.com/Avaiga/taipy-config).
+- [taipy-core](https://github.com/Avaiga/taipy-core).
+- [taipy-gui](https://github.com/Avaiga/taipy-gui).
+- [taipy-rest](https://github.com/Avaiga/taipy-rest).
+- [taipy](https://github.com/Avaiga/taipy) brings previous packages in a single one.
+
+## Coding style and best practices
+
+### Python
+
+Taipy's repositories follow the [PEP 8](https://www.python.org/dev/peps/pep-0008/) and
+[PEP 484](https://www.python.org/dev/peps/pep-0484/) coding convention.
+
+## TypeScript
+
+Taipy's repositories use the [ESLint](https://eslint.org/) and
+[TypeScript ESLint](https://github.com/typescript-eslint/typescript-eslint) plugin to ensure a common set of rules.
+
+### Git branches
+
+All new development happens in the `develop` branch. All pull requests should target that branch.
+We are following a strict branch naming convention based on the pattern: `<type>/#<issueId>[IssueSummary]`.
+
+Where:
+
+- `<type>` would be one of:
+    - feature: new feature implementation, or improvement of a feature.
+    - bug: bug fix.
+    - review: change provoked by review comment not immediately taken care of.
+    - refactor: refactor of a piece of code.
+    - doc: doc changes (complement or typo fixes…).
+    - build: in relation with the build process.
+- `<issueId>` is the processed issue identifier. The advantage of explicitly indicating the issue number is that in
+  GitHub, a pull request page shows a direct link to the issue description.
+- `[IssueSummary]` is a short summary of the issue topic, not including spaces, using Camel case or lower-case,
+  dash-separated words. This summary, with its dash (‘-’) symbol prefix, is optional.
+
+
+## Contribution workflow
+
+Find an issue without the label `current sprint` and add a comment on it to inform the community that you are
+working on it.
+
+1. Make your [own fork](https://help.github.com/en/github/getting-started-with-github/fork-a-repo) of the repository
+   target by the issue. Clone it on our local machine, then go inside the directory.
+
+2. We are working with [Pipenv](https://github.com/pypa/pipenv) for our virtualenv.
+   Create a local env and install development package by running `pipenv install --dev`, then run tests with `pipenv
+   run pytest` to verify your setup.
+
+3. For convention help, we provide a [pre-commit](https://pre-commit.com/hooks.html) file.
+   This tool will run before each commit and will automatically reformat code or raise warnings and errors based on the
+   code format or Python typing.
+   You can install and setup it up by doing:
+   ```
+     pipenv install pre-commit
+     pipenv run python -m pre-commit install
+   ```
+
+4. Make the changes.<br/>
+   You may want to also add your GitHub login as a new line of the `contributors.txt` file located at the root
+   of this repository. We are using it to list our contributors in the Taipy documentation
+   (see the "Contributing > Contributors" section) and thank them.
+
+5. Create a [pull request from your fork](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request-from-a-fork).<br/>
+   Keep your pull request as __draft__ until your work is finished.
+   Do not hesitate to add a comment for help or questions.
+   Before you submit a pull request for review from your forked repo, check that it meets these guidelines:
+    - Include tests.
+    - Code is [rebase](http://stackoverflow.com/a/7244456/1110993).
+    - License is present.
+    - pre-commit works - without mypy error.
+    - GitHub's actions are passing.
+
+6. The taipy team will have a look at your Pull Request and will give feedback. If every requirement is valid, your
+   work will be added in the next release, congratulation!
+
+
+## Dependency management
+
+Taipy comes with multiple optional packages. You can find the list directly in the product or Taipy's packages.
+The back-end Pipfile does not install by default optional packages due to `pyodbc` requiring a driver's manual
+installation. This is not the behaviour for the front-end that installs all optional packages through its Pipfile.
+
+If you are a contributor on Taipy, be careful with dependencies, do not forget to install or uninstall depending on
+your issue.
+
+If you need to add a new dependency to Taipy, do not forget to add it in the `Pipfile` and the `setup.py`.
+Keep in mind that dependency is a vector of attack. The Taipy team limits the usage of external dependencies at the
+minimum.

+ 202 - 0
src/taipy/templates/LICENSE

@@ -0,0 +1,202 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright 2023 Avaiga Private Limited
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.

+ 1 - 0
src/taipy/templates/MANIFEST.in

@@ -0,0 +1 @@
+recursive-include src/taipy/templates *

+ 26 - 0
src/taipy/templates/Pipfile

@@ -0,0 +1,26 @@
+[[source]]
+url = "https://pypi.org/simple"
+verify_ssl = true
+name = "pypi"
+
+[packages]
+
+[dev-packages]
+autopep8 = "*"
+black = "*"
+cookiecutter = "==2.1.1"
+flake8 = "*"
+flake8-docstrings = "*"
+isort = "*"
+mypy = "*"
+pre-commit = "*"
+pytest = "*"
+taipy = {ref = "develop", git = "https://github.com/avaiga/taipy.git"}
+tox = "*"
+types-python-dateutil = "*"
+
+[requires]
+python_version = "3"
+
+[pipenv]
+allow_prereleases = true

+ 63 - 0
src/taipy/templates/README.md

@@ -0,0 +1,63 @@
+# Taipy templates
+
+## License
+Copyright 2023 Avaiga Private Limited
+
+Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+except in compliance with the License. You may obtain a copy of the License at
+[http://www.apache.org/licenses/LICENSE-2.0](https://www.apache.org/licenses/LICENSE-2.0.txt)
+
+Unless required by applicable law or agreed to in writing, software distributed under the 
+License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+either express or implied. See the License for the specific language governing permissions
+and limitations under the License.
+
+## Usage
+  - [Taipy](#taipy)
+  - [License](#license)
+  - [Usage](#usage)
+  - [What is Taipy](#what-is-taipy)
+  - [Contributing](#contributing)
+  - [Code of conduct](#code-of-conduct)
+  - [Directory Structure](#directory-structure)
+
+## What is Taipy templates
+
+Taipy is a Python library for creating Business Applications. More information on our 
+[website](https://www.taipy.io).
+
+Taipy templates is a repository that contains templates and scaffoldings created and 
+maintained by Taipy. It helps users getting started with a simple and ready-to-go application.
+
+A more in depth documentation of taipy can be found [here](https://docs.taipy.io).
+
+To create a Taipy application using this template, first you need to install Taipy (> 2.2). 
+Then from a terminal, run the following command.
+```
+$ taipy create
+```
+or
+```
+$ taipy create --template "default"
+```
+
+After providing necessary information, your new application is created in the current 
+working directory.
+
+## Contributing
+
+Want to help build _Taipy_? Check out our [`CONTRIBUTING.md`](CONTRIBUTING.md) file.
+
+## Code of conduct
+
+Want to be part of the _Taipy_ community? Check out our 
+[`CODE_OF_CONDUCT.md`](CODE_OF_CONDUCT.md) file.
+
+## Directory Structure
+
+- `src/taipy/templates/`: Contains each template in a dedicated sub-folder with the 
+    following structure:
+- `CODE_OF_CONDUCT.md`: Code of conduct for members and contributors of _taipy_.
+- `CONTRIBUTING.md`: Instructions to contribute to _taipy_.
+- `LICENSE`: The Apache 2.0 License.
+- `README.md`: Current file.

+ 15 - 0
src/taipy/templates/default/cookiecutter.json

@@ -0,0 +1,15 @@
+{
+    "Application root folder name": "taipy_application",
+    "Application main Python file": "main.py",
+    "Application title": "Taipy Application",
+    "Page names in multi-page application?": "",
+    "Does the application use scenario management or version management?": "No",
+    "Does the application use Rest API?": "No",
+
+    "__root_folder_name": "{{ cookiecutter['Application root folder name'] | replace(' ','-') }}",
+    "__main_file": "{{ cookiecutter['Application main Python file'][:-3] if cookiecutter['Application main Python file'].endswith('.py') else cookiecutter['Application main Python file'] }}",
+    "__application_title": "{{ cookiecutter['Application title'] }}",
+    "__pages": "{{ cookiecutter['Page names in multi-page application?'] }}",
+    "__core": "{{ cookiecutter['Does the application use scenario management or version management?'] }}",
+    "__rest": "{{ cookiecutter['Does the application use Rest API?'] }}"
+}

+ 180 - 0
src/taipy/templates/default/hooks/post_gen_project.py

@@ -0,0 +1,180 @@
+# Copyright 2023 Avaiga Private Limited
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+#        http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+# an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations under the License.
+
+import os
+import shutil
+
+import taipy
+
+
+def handle_services(use_rest, use_core):
+    if use_core or use_rest:
+        # Write "import taipy as tp" at the third line of the import.txt file
+        with open(os.path.join(os.getcwd(), "sections", "import.txt"), "r") as import_file:
+            import_lines = import_file.readlines()
+            import_lines[0] = "import taipy as tp\n" + import_lines[0] + "\n"
+        with open(os.path.join(os.getcwd(), "sections", "import.txt"), "w") as import_file:
+            import_file.writelines(import_lines)
+
+    # Import the necessary services
+    if use_core and use_rest:
+        with open(os.path.join(os.getcwd(), "sections", "import.txt"), "a") as import_file:
+            import_file.write("from taipy import Core, Rest\n")
+    elif use_core:
+        with open(os.path.join(os.getcwd(), "sections", "import.txt"), "a") as import_file:
+            import_file.write("from taipy import Core\n")
+    elif use_rest:
+        with open(os.path.join(os.getcwd(), "sections", "import.txt"), "a") as import_file:
+            import_file.write("from taipy import Rest\n")
+
+    # Start the Rest service
+    if use_rest:
+        with open(os.path.join(os.getcwd(), "sections", "main.txt"), "a") as main_file:
+            main_file.write("    rest = Rest()\n")
+
+    if use_core:
+        # Create and submit the placeholder scenario
+        with open(os.path.join(os.getcwd(), "sections", "main.txt"), "a") as main_file:
+            main_file.write("    core = Core()\n")
+            main_file.write("    core.run()\n")
+            main_file.write("    # #############################################################################\n")
+            main_file.write("    # PLACEHOLDER: Create and submit your scenario here                           #\n")
+            main_file.write("    #                                                                             #\n")
+            main_file.write("    # Example:                                                                    #\n")
+            main_file.write("    # from configuration import scenario_config                                   #\n")
+            main_file.write("    # scenario = tp.create_scenario(scenario_config)                              #\n")
+            main_file.write("    # scenario.submit()                                                           #\n")
+            main_file.write("    # Comment, remove or replace the previous lines with your own use case        #\n")
+            main_file.write("    # #############################################################################\n")
+    else:
+        shutil.rmtree(os.path.join(os.getcwd(), "algorithms"))
+        shutil.rmtree(os.path.join(os.getcwd(), "configuration"))
+
+
+def handle_run_service():
+    with open(os.path.join(os.getcwd(), "sections", "main.txt"), "a+") as main_file:
+        main_file.seek(0)
+        main_content = main_file.read()
+        # Run Rest service along with the GUI service
+        if "rest = Rest()" in main_content:
+            main_file.write('    tp.run(gui, rest, title="{{cookiecutter.__application_title}}")\n')
+        else:
+            main_file.write('    gui.run(title="{{cookiecutter.__application_title}}")\n')
+
+
+def handle_single_page_app():
+    shutil.rmtree(os.path.join(os.getcwd(), "pages"))
+
+    with open(os.path.join(os.getcwd(), "sections", "main.txt"), "a") as main_file:
+        main_file.write("\n")
+        main_file.write("    gui = Gui(page=page)\n")
+
+    handle_run_service()
+
+    with open(os.path.join(os.getcwd(), "sections", "page_content.txt"), "a") as page_content_file:
+        page_content_file.write(
+            '''
+page = """
+<center>
+<|navbar|lov={[("home", "Homepage")]}|>
+</center>
+
+"""
+'''
+        )
+
+
+def handle_multi_page_app(pages):
+    for page_name in pages:
+        os.mkdir(os.path.join(os.getcwd(), "pages", page_name))
+        with open(os.path.join(os.getcwd(), "pages", "page_example", "page_example.md"), "r") as page_md_file:
+            page_md_content = page_md_file.read()
+        page_md_content = page_md_content.replace("Page example", page_name.replace("_", " ").title())
+        with open(os.path.join(os.getcwd(), "pages", page_name, page_name + ".md"), "w") as page_md_file:
+            page_md_file.write(page_md_content)
+
+        with open(os.path.join(os.getcwd(), "pages", "page_example", "page_example.py"), "r") as page_content_file:
+            page_py_content = page_content_file.read()
+        page_py_content = page_py_content.replace("page_example", page_name)
+        with open(os.path.join(os.getcwd(), "pages", page_name, page_name + ".py"), "w") as page_content_file:
+            page_content_file.write(page_py_content)
+
+    with open(os.path.join(os.getcwd(), "pages", "__init__.py"), "a") as page_init_file:
+        for page_name in pages:
+            page_init_file.write(f"from .{page_name}.{page_name} import {page_name}\n")
+
+    shutil.rmtree(os.path.join(os.getcwd(), "pages", "page_example"))
+
+    newline = ",\n\t"
+    user_page_dict = newline.join(f'"{page_name}": {page_name}' for page_name in pages)
+    page_dict = """
+pages = {
+    "/": root_page,
+    {pages}
+}
+"""
+    with open(os.path.join(os.getcwd(), "sections", "page_content.txt"), "a") as page_content_file:
+        page_content_file.write(page_dict.replace("{pages}", user_page_dict))
+
+    with open(os.path.join(os.getcwd(), "sections", "import.txt"), "a") as import_file:
+        import_file.write("from pages import *\n")
+
+    with open(os.path.join(os.getcwd(), "sections", "main.txt"), "a") as main_file:
+        main_file.write("\n")
+        main_file.write("    gui = Gui(pages=pages)\n")
+
+    handle_run_service()
+
+
+def generate_main_file():
+    with open(os.path.join(os.getcwd(), "sections", "import.txt"), "r") as import_file:
+        import_lines = import_file.read()
+    with open(os.path.join(os.getcwd(), "sections", "page_content.txt"), "r") as page_content_file:
+        page_content = page_content_file.read()
+    with open(os.path.join(os.getcwd(), "sections", "main.txt"), "r") as main_file:
+        main_lines = main_file.read()
+
+    with open(os.path.join(os.getcwd(), "{{cookiecutter.__main_file}}.py"), "a") as app_main_file:
+        app_main_file.write(import_lines)
+        app_main_file.write("\n")
+        app_main_file.write(page_content)
+        app_main_file.write("\n\n")
+        app_main_file.write(main_lines)
+
+
+with open(os.path.join(os.getcwd(), "requirements.txt"), "a") as requirement_file:
+    requirement_file.write(f"taipy=={taipy.version._get_version()}\n")
+
+use_core = "{{ cookiecutter.__core }}".upper()
+use_rest = "{{ cookiecutter.__rest }}".upper()
+handle_services(use_rest in ["YES", "Y"], use_core in ["YES", "Y"])
+
+pages = "{{ cookiecutter.__pages }}".split(" ")
+# Remove empty string from pages list
+pages = [page for page in pages if page != ""]
+if len(pages) == 0:
+    handle_single_page_app()
+else:
+    handle_multi_page_app(pages)
+
+generate_main_file()
+
+# Remove the sections folder
+shutil.rmtree(os.path.join(os.getcwd(), "sections"))
+
+main_file_name = "{{cookiecutter.__main_file}}.py"
+print(
+    f"New Taipy application has been created at {os.path.join(os.getcwd())}"
+    f"\n\nTo start the application, change directory to the newly created folder:"
+    f"\n\tcd {os.path.join(os.getcwd())}"
+    f"\nand run the application as follows:"
+    f"\n\ttaipy run {main_file_name}"
+)

+ 9 - 0
src/taipy/templates/default/hooks/pre_gen_project.py

@@ -0,0 +1,9 @@
+import sys
+
+pages = "{{ cookiecutter.__pages }}".split(" ")
+# Remove empty string from pages list
+pages = [page for page in pages if page != ""]
+
+for page in pages:
+    if not page.isidentifier():
+        sys.exit(f'Page name "{page}" is not a valid Python identifier. Please choose another name.')

+ 1 - 0
src/taipy/templates/default/{{cookiecutter.__root_folder_name}}/algorithms/__init__.py

@@ -0,0 +1 @@
+from algorithms import *

+ 14 - 0
src/taipy/templates/default/{{cookiecutter.__root_folder_name}}/algorithms/algorithms.py

@@ -0,0 +1,14 @@
+"""
+This file is designed to contain the various Python functions used to configure tasks.
+
+The functions will be imported by the __init__.py file in this folder.
+"""
+
+# ##################################################################################################################
+# PLACEHOLDER: Put your Python functions here                                                                      #
+#                                                                                                                  #
+# Example:                                                                                                         #
+# def place_holder_algorithm():                                                                                    #
+#     pass                                                                                                         #
+# Comment, remove or replace the previous lines with your own use case                                             #
+# ##################################################################################################################

+ 1 - 0
src/taipy/templates/default/{{cookiecutter.__root_folder_name}}/configuration/__init__.py

@@ -0,0 +1 @@
+from .config import *

+ 17 - 0
src/taipy/templates/default/{{cookiecutter.__root_folder_name}}/configuration/config.py

@@ -0,0 +1,17 @@
+"""
+Contain the application's configuration including the scenario configurations.
+
+The configuration is run by the Core service.
+"""
+
+from algorithms import *
+
+from taipy import Config
+
+# #############################################################################
+# PLACEHOLDER: Put your application's configurations here                     #
+#                                                                             #
+# Example:                                                                    #
+# scenario_config = Config.configure_scenario("placeholder_scenario", [])     #
+# Comment, remove or replace the previous lines with your own use case        #
+# #############################################################################

+ 1 - 0
src/taipy/templates/default/{{cookiecutter.__root_folder_name}}/pages/__init__.py

@@ -0,0 +1 @@
+from .root import root_page

+ 1 - 0
src/taipy/templates/default/{{cookiecutter.__root_folder_name}}/pages/page_example/page_example.md

@@ -0,0 +1 @@
+# Page example

+ 10 - 0
src/taipy/templates/default/{{cookiecutter.__root_folder_name}}/pages/page_example/page_example.py

@@ -0,0 +1,10 @@
+"""
+A page of the application.
+Page content is imported from the page_example.md file.
+
+Please refer to https://docs.taipy.io/en/latest/manuals/gui/pages for more details.
+"""
+
+from taipy.gui import Markdown
+
+page_example = Markdown("pages/page_example/page_example.md")

+ 3 - 0
src/taipy/templates/default/{{cookiecutter.__root_folder_name}}/pages/root.md

@@ -0,0 +1,3 @@
+<center>
+<|navbar|>
+</center>

+ 10 - 0
src/taipy/templates/default/{{cookiecutter.__root_folder_name}}/pages/root.py

@@ -0,0 +1,10 @@
+"""
+The root page of the application.
+Page content is imported from the root.md file.
+
+Please refer to https://docs.taipy.io/en/latest/manuals/gui/pages for more details.
+"""
+
+from taipy.gui import Markdown
+
+root_page = Markdown("pages/root.md")

+ 0 - 0
src/taipy/templates/default/{{cookiecutter.__root_folder_name}}/requirements.txt


+ 1 - 0
src/taipy/templates/default/{{cookiecutter.__root_folder_name}}/sections/import.txt

@@ -0,0 +1 @@
+from taipy.gui import Gui

+ 1 - 0
src/taipy/templates/default/{{cookiecutter.__root_folder_name}}/sections/main.txt

@@ -0,0 +1 @@
+if __name__ == "__main__":

+ 0 - 0
src/taipy/templates/default/{{cookiecutter.__root_folder_name}}/sections/page_content.txt


+ 0 - 0
src/taipy/templates/default/{{cookiecutter.__root_folder_name}}/{{cookiecutter.__main_file}}.py


+ 2 - 0
src/taipy/templates/mypy.ini

@@ -0,0 +1,2 @@
+[mypy]
+ignore_missing_imports = True

+ 4 - 0
src/taipy/templates/pyproject.toml

@@ -0,0 +1,4 @@
+[tool.black]
+line-length = 120
+
+[tool.pyright]

+ 11 - 0
src/taipy/templates/scenario-management/cookiecutter.json

@@ -0,0 +1,11 @@
+{
+    "Application root folder name": "taipy_application",
+    "Application main Python file": "main.py",
+    "Application title": "Taipy Application",
+    "Does the application use TOML Config?": "No",
+
+    "__root_folder_name": "{{ cookiecutter['Application root folder name'] | replace(' ','-') }}",
+    "__main_file": "{{ cookiecutter['Application main Python file'][:-3] if cookiecutter['Application main Python file'].endswith('.py') else cookiecutter['Application main Python file'] }}",
+    "__application_title": "{{ cookiecutter['Application title'] }}",
+    "__use_toml_config": "{{ cookiecutter['Does the application use TOML Config?'] }}"
+}

+ 38 - 0
src/taipy/templates/scenario-management/hooks/post_gen_project.py

@@ -0,0 +1,38 @@
+# Copyright 2023 Avaiga Private Limited
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+#        http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+# an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations under the License.
+
+import os
+
+import taipy
+
+# Add taipy version to requirements.txt
+with open(os.path.join(os.getcwd(), "requirements.txt"), "a") as requirement_file:
+    requirement_file.write(f"taipy=={taipy.version._get_version()}\n")
+
+# Use TOML config file or not
+use_toml_config = "{{ cookiecutter.__use_toml_config }}".upper()
+if use_toml_config == "YES" or use_toml_config == "Y":
+    os.remove(os.path.join(os.getcwd(), "config", "config.py"))
+    os.rename(
+        os.path.join(os.getcwd(), "config", "config_with_toml.py"), os.path.join(os.getcwd(), "config", "config.py")
+    )
+else:
+    os.remove(os.path.join(os.getcwd(), "config", "config_with_toml.py"))
+    os.remove(os.path.join(os.getcwd(), "config", "config.toml"))
+
+main_file_name = "{{cookiecutter.__main_file}}.py"
+print(
+    f"New Taipy application has been created at {os.path.join(os.getcwd())}"
+    f"\n\nTo start the application, change directory to the newly created folder:"
+    f"\n\tcd {os.path.join(os.getcwd())}"
+    f"\nand run the application as follows:"
+    f"\n\ttaipy run {main_file_name}"
+)

+ 2 - 0
src/taipy/templates/scenario-management/{{cookiecutter.__root_folder_name}}/.gitignore

@@ -0,0 +1,2 @@
+# Taipy
+.data

+ 0 - 0
src/taipy/templates/scenario-management/{{cookiecutter.__root_folder_name}}/.taipyignore


+ 1 - 0
src/taipy/templates/scenario-management/{{cookiecutter.__root_folder_name}}/algos/__init__.py

@@ -0,0 +1 @@
+from .algos import clean_data

+ 3 - 0
src/taipy/templates/scenario-management/{{cookiecutter.__root_folder_name}}/algos/algos.py

@@ -0,0 +1,3 @@
+def clean_data(df, replacement_type):
+    df = df.fillna(replacement_type)
+    return df

+ 0 - 0
src/taipy/templates/scenario-management/{{cookiecutter.__root_folder_name}}/config/__init__.py


+ 25 - 0
src/taipy/templates/scenario-management/{{cookiecutter.__root_folder_name}}/config/config.py

@@ -0,0 +1,25 @@
+from algos import clean_data
+
+from taipy import Config, Frequency, Scope
+
+
+def configure():
+    # ##################################################################################################################
+    # PLACEHOLDER: Add your scenario configurations here                                                               #
+    #                                                                                                                  #
+    # Example:                                                                                                         #
+    initial_dataset_cfg = Config.configure_csv_data_node("initial_dataset", scope=Scope.CYCLE)
+    replacement_type_cfg = Config.configure_data_node("replacement_type", default_data="NO VALUE")
+    cleaned_dataset_cfg = Config.configure_csv_data_node("cleaned_dataset")
+    clean_data_cfg = Config.configure_task(
+        "clean_data",
+        function=clean_data,
+        input=[initial_dataset_cfg, replacement_type_cfg],
+        output=cleaned_dataset_cfg,
+    )
+    scenario_cfg = Config.configure_scenario(
+        "scenario_configuration", task_configs=[clean_data_cfg], frequency=Frequency.DAILY
+    )
+    return scenario_cfg
+    # Comment, remove or replace the previous lines with your own use case                                             #
+    # ##################################################################################################################

+ 24 - 0
src/taipy/templates/scenario-management/{{cookiecutter.__root_folder_name}}/config/config.toml

@@ -0,0 +1,24 @@
+[TAIPY]
+
+[DATA_NODE.initial_dataset]
+storage_type = "csv"
+scope = "CYCLE:SCOPE"
+
+[DATA_NODE.replacement_type]
+default_data = "NO VALUE"
+
+[DATA_NODE.cleaned_dataset]
+storage_type = "csv"
+
+[TASK.clean_data]
+function = "algos.algos.clean_data:function"
+inputs = [ "initial_dataset:SECTION", "replacement_type:SECTION",]
+outputs = [ "cleaned_dataset:SECTION",]
+skippable = "False:bool"
+
+[SCENARIO.scenario_configuration]
+tasks = [ "clean_data:SECTION",]
+frequency = "DAILY:FREQUENCY"
+sequences.scenario_configuration_sequence = [ "clean_data:SECTION",]
+
+[SCENARIO.scenario_configuration.comparators]

+ 6 - 0
src/taipy/templates/scenario-management/{{cookiecutter.__root_folder_name}}/config/config_with_toml.py

@@ -0,0 +1,6 @@
+from taipy import Config
+
+
+def configure():
+    Config.load("config/config.toml")
+    return Config.scenarios["scenario_configuration"]

+ 2 - 0
src/taipy/templates/scenario-management/{{cookiecutter.__root_folder_name}}/pages/__init__.py

@@ -0,0 +1,2 @@
+from .job_page import job_page
+from .scenario_page import scenario_page

+ 1 - 0
src/taipy/templates/scenario-management/{{cookiecutter.__root_folder_name}}/pages/job_page/__init__.py

@@ -0,0 +1 @@
+from .job_page import job_page

+ 1 - 0
src/taipy/templates/scenario-management/{{cookiecutter.__root_folder_name}}/pages/job_page/job_page.md

@@ -0,0 +1 @@
+<|job_selector|>

+ 3 - 0
src/taipy/templates/scenario-management/{{cookiecutter.__root_folder_name}}/pages/job_page/job_page.py

@@ -0,0 +1,3 @@
+from taipy.gui import Markdown
+
+job_page = Markdown("pages/job_page/job_page.md")

+ 21 - 0
src/taipy/templates/scenario-management/{{cookiecutter.__root_folder_name}}/pages/root.md

@@ -0,0 +1,21 @@
+<|layout|columns=1 5|
+
+<|sidebar|
+
+<|{selected_scenario}|scenario_selector|>
+
+<|part|render={selected_scenario}|
+<|{selected_data_node}|data_node_selector|not display_cycles|>
+|>
+|>
+
+<|part|class_name=main|
+
+<|navbar|>
+
+<|part|class_name=main|
+<|content|>
+|>
+
+|>
+|>

+ 7 - 0
src/taipy/templates/scenario-management/{{cookiecutter.__root_folder_name}}/pages/root.py

@@ -0,0 +1,7 @@
+from taipy.gui import Markdown
+
+selected_scenario = None
+selected_data_node = None
+content = ""
+
+root = Markdown("pages/root.md")

+ 1 - 0
src/taipy/templates/scenario-management/{{cookiecutter.__root_folder_name}}/pages/scenario_page/__init__.py

@@ -0,0 +1 @@
+from .scenario_page import scenario_page

+ 39 - 0
src/taipy/templates/scenario-management/{{cookiecutter.__root_folder_name}}/pages/scenario_page/data_node_management.py

@@ -0,0 +1,39 @@
+# build partial content for a specific data node
+def build_dn_partial(dn, dn_label):
+    partial_content = "<|part|render={selected_scenario}|\n\n"
+
+    # ##################################################################################################################
+    # PLACEHOLDER: data node specific content before automatic content                                                 #
+    #                                                                                                                  #
+    # Example:                                                                                                         #
+    if dn_label == "replacement_type":
+        partial_content += "All missing values will be replaced by the data node value."
+    # Comment, remove or replace the previous lines with your own use case                                             #
+    # ##################################################################################################################
+
+    # Automatic data node content
+    partial_content += (
+        "<|{selected_scenario.data_nodes['" + dn.config_id + "']}|data_node|scenario={" "selected_scenario}|>\n\n "
+    )
+
+    # ##################################################################################################################
+    # PLACEHOLDER: data node specific content after automatic content                                                  #
+    #                                                                                                                  #
+    # Example:                                                                                                         #
+    if dn_label == "initial_dataset":
+        partial_content += (
+            "Select your CSV file: <|{selected_data_node.path}|file_selector|extensions=.csv|on_action"
+            "={lambda s: s.refresh('selected_scenario')}|>\n\n "
+        )
+    # Comment, remove or replace the previous lines with your own use case                                             #
+    # ##################################################################################################################
+
+    partial_content += "|>\n\n"
+    return partial_content
+
+
+def manage_partial(state):
+    dn = state.selected_data_node
+    dn_label = dn.get_simple_label()
+    partial_content = build_dn_partial(dn, dn_label)
+    state.data_node_partial.update_content(state, partial_content)

+ 12 - 0
src/taipy/templates/scenario-management/{{cookiecutter.__root_folder_name}}/pages/scenario_page/scenario_page.md

@@ -0,0 +1,12 @@
+<|layout|columns=1 1|
+
+<|part|render={selected_scenario}|
+
+<|{selected_scenario}|scenario|not expandable|expanded|on_submission_change=notify_on_submission|>
+
+<|{selected_scenario}|scenario_dag|>
+|>
+
+<|part|partial={data_node_partial}|render={selected_data_node}|>
+
+|>

+ 19 - 0
src/taipy/templates/scenario-management/{{cookiecutter.__root_folder_name}}/pages/scenario_page/scenario_page.py

@@ -0,0 +1,19 @@
+from taipy.gui import Markdown, notify
+
+from .data_node_management import manage_partial
+
+
+def notify_on_submission(state, submitable, details):
+    if details["submission_status"] == "COMPLETED":
+        notify(state, "success", "Submision completed!")
+    elif details["submission_status"] == "FAILED":
+        notify(state, "error", "Submission failed!")
+    else:
+        notify(state, "info", "In progress...")
+
+
+def manage_data_node_partial(state):
+    manage_partial(state)
+
+
+scenario_page = Markdown("pages/scenario_page/scenario_page.md")

+ 1 - 0
src/taipy/templates/scenario-management/{{cookiecutter.__root_folder_name}}/requirements.txt

@@ -0,0 +1 @@
+taipy

+ 43 - 0
src/taipy/templates/scenario-management/{{cookiecutter.__root_folder_name}}/{{cookiecutter.__main_file}}.py

@@ -0,0 +1,43 @@
+from config.config import configure
+from pages import job_page, scenario_page
+from pages.root import content, root, selected_data_node, selected_scenario
+
+import taipy as tp
+from taipy import Core, Gui
+
+
+def on_init(state):
+    ...
+
+
+def on_change(state, var, val):
+    if var == "selected_data_node" and val:
+        state["scenario"].manage_data_node_partial(state)
+
+
+pages = {
+    "/": root,
+    "scenario": scenario_page,
+    "jobs": job_page,
+}
+
+
+if __name__ == "__main__":
+    # Instantiate, configure and run the Core
+    core = Core()
+    default_scenario_cfg = configure()
+    core.run()
+
+    # ##################################################################################################################
+    # PLACEHOLDER: Initialize your data application here                                                               #
+    #                                                                                                                  #
+    # Example:                                                                                                         #
+    if len(tp.get_scenarios()) == 0:
+        tp.create_scenario(default_scenario_cfg, name="Default Scenario")
+    # Comment, remove or replace the previous lines with your own use case                                             #
+    # ##################################################################################################################
+
+    # Instantiate, configure and run the GUI
+    gui = Gui(pages=pages)
+    data_node_partial = gui.add_partial("")
+    gui.run(title="{{cookiecutter.__application_title}}", margin="0em")

+ 57 - 0
src/taipy/templates/setup.py

@@ -0,0 +1,57 @@
+# Copyright 2023 Avaiga Private Limited
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+#        http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+# an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations under the License.
+
+"""The setup script."""
+
+import json
+import os
+
+from setuptools import find_namespace_packages, find_packages, setup
+
+with open("README.md", "rb") as readme_file:
+    readme = readme_file.read().decode("UTF-8")
+
+with open(f"src{os.sep}taipy{os.sep}templates{os.sep}version.json") as version_file:
+    version = json.load(version_file)
+    version_string = f'{version.get("major", 0)}.{version.get("minor", 0)}.{version.get("patch", 0)}'
+    if vext := version.get("ext"):
+        version_string = f"{version_string}.{vext}"
+
+test_requirements = ["pytest>=3.8"]
+
+setup(
+    author="Avaiga",
+    author_email="dev@taipy.io",
+    python_requires=">=3.8",
+    classifiers=[
+        "Intended Audience :: Developers",
+        "License :: OSI Approved :: Apache Software License",
+        "Natural Language :: English",
+        "Programming Language :: Python :: 3",
+        "Programming Language :: Python :: 3.8",
+        "Programming Language :: Python :: 3.9",
+        "Programming Language :: Python :: 3.10",
+        "Programming Language :: Python :: 3.11",
+    ],
+    description="An open-source package holding Taipy application templates.",
+    license="Apache License 2.0",
+    long_description=readme,
+    long_description_content_type="text/markdown",
+    keywords="taipy-templates",
+    name="taipy-templates",
+    package_dir={"": "src"},
+    packages=find_namespace_packages(where="src") + find_packages(include=["taipy"]),
+    include_package_data=True,
+    test_suite="tests",
+    url="https://github.com/avaiga/taipy-templates",
+    version=version_string,
+    zip_safe=False,
+)

+ 10 - 0
src/taipy/templates/tests/__init__.py

@@ -0,0 +1,10 @@
+# Copyright 2023 Avaiga Private Limited
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+#        http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+# an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations under the License.

+ 195 - 0
src/taipy/templates/tests/test_default_template.py

@@ -0,0 +1,195 @@
+# Copyright 2023 Avaiga Private Limited
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+#        http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+# an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations under the License.
+
+import os
+
+import pytest
+from cookiecutter.exceptions import FailedHookException
+from cookiecutter.main import cookiecutter
+
+from .utils import _run_template
+
+
+def test_default_answer(tmpdir):
+    cookiecutter(
+        template="src/taipy/templates/default",
+        output_dir=str(tmpdir),
+        no_input=True,
+    )
+
+    assert os.listdir(tmpdir) == ["taipy_application"]
+    assert (
+        os.listdir(os.path.join(tmpdir, "taipy_application")).sort() == ["requirements.txt", "main.py", "images"].sort()
+    )
+    with open(os.path.join(tmpdir, "taipy_application", "requirements.txt")) as requirements_file:
+        # Assert post_gen_project hook is successful
+        assert "taipy==" in requirements_file.read()
+
+    oldpwd = os.getcwd()
+    os.chdir(os.path.join(tmpdir, "taipy_application"))
+    stdout = _run_template("main.py")
+    os.chdir(oldpwd)
+
+    # Assert the message when the application is run successfully is in the stdout
+    assert "[Taipy][INFO]  * Server starting on" in str(stdout, "utf-8")
+
+
+def test_main_file_with_and_without_extension(tmpdir):
+    cookiecutter(
+        template="src/taipy/templates/default",
+        output_dir=str(tmpdir),
+        no_input=True,
+        extra_context={
+            "Application main Python file": "app.py",
+        },
+    )
+    assert (
+        os.listdir(os.path.join(tmpdir, "taipy_application")).sort() == ["requirements.txt", "app.py", "images"].sort()
+    )
+
+    cookiecutter(
+        template="src/taipy/templates/default",
+        output_dir=str(tmpdir),
+        no_input=True,
+        extra_context={
+            "Application root folder name": "foo_app",
+            "Application main Python file": "app",
+        },
+    )
+    assert os.listdir(os.path.join(tmpdir, "foo_app")).sort() == ["requirements.txt", "app.py", "images"].sort()
+
+
+def test_with_core_service(tmpdir):
+    cookiecutter(
+        template="src/taipy/templates/default",
+        output_dir=str(tmpdir),
+        no_input=True,
+        extra_context={
+            "Does the application use scenario management or version management?": "y",
+            "Does the application use Rest API?": "no",
+        },
+    )
+
+    assert (
+        os.listdir(os.path.join(tmpdir, "taipy_application")).sort()
+        == ["requirements.txt", "main.py", "images", "configuration", "algorithms"].sort()
+    )
+    with open(os.path.join(tmpdir, "taipy_application", "main.py")) as main_file:
+        assert "core = Core()" in main_file.read()
+
+    oldpwd = os.getcwd()
+    os.chdir(os.path.join(tmpdir, "taipy_application"))
+    stdout = _run_template("main.py")
+    os.chdir(oldpwd)
+
+    # Assert the message when the application is run successfully is in the stdout
+    assert "[Taipy][INFO]  * Server starting on" in str(stdout, "utf-8")
+    assert "[Taipy][INFO] Development mode: " in str(stdout, "utf-8")
+
+
+def test_with_rest_service(tmpdir):
+    cookiecutter(
+        template="src/taipy/templates/default",
+        output_dir=str(tmpdir),
+        no_input=True,
+        extra_context={
+            "Does the application use scenario management or version management?": "n",
+            "Does the application use Rest API?": "yes",
+        },
+    )
+
+    assert (
+        os.listdir(os.path.join(tmpdir, "taipy_application")).sort() == ["requirements.txt", "main.py", "images"].sort()
+    )
+    with open(os.path.join(tmpdir, "taipy_application", "main.py")) as main_file:
+        assert "rest = Rest()" in main_file.read()
+
+    oldpwd = os.getcwd()
+    os.chdir(os.path.join(tmpdir, "taipy_application"))
+    stdout = _run_template("main.py")
+    os.chdir(oldpwd)
+
+    # Assert the message when the application is run successfully is in the stdout
+    assert "[Taipy][INFO]  * Server starting on" in str(stdout, "utf-8")
+    assert "[Taipy][INFO] Development mode: " in str(stdout, "utf-8")
+
+
+def test_with_both_core_rest_services(tmpdir):
+    cookiecutter(
+        template="src/taipy/templates/default",
+        output_dir=str(tmpdir),
+        no_input=True,
+        extra_context={
+            "Does the application use scenario management or version management?": "n",
+            "Does the application use Rest API?": "yes",
+        },
+    )
+
+    assert (
+        os.listdir(os.path.join(tmpdir, "taipy_application")).sort()
+        == ["requirements.txt", "main.py", "images", "configuration", "algorithms"].sort()
+    )
+    with open(os.path.join(tmpdir, "taipy_application", "main.py")) as main_file:
+        assert "rest = Rest()" in main_file.read()
+        assert "core = Core()" not in main_file.read()
+
+    oldpwd = os.getcwd()
+    os.chdir(os.path.join(tmpdir, "taipy_application"))
+    stdout = _run_template("main.py")
+    os.chdir(oldpwd)
+
+    # Assert the message when the application is run successfully is in the stdout
+    assert "[Taipy][INFO]  * Server starting on" in str(stdout, "utf-8")
+    assert "[Taipy][INFO] Development mode: " in str(stdout, "utf-8")
+
+
+def test_multipage_gui_template(tmpdir):
+    cookiecutter(
+        template="src/taipy/templates/default",
+        output_dir=str(tmpdir),
+        no_input=True,
+        extra_context={
+            "Application root folder name": "foo_app",
+            "Page names in multi-page application?": "name_1 name_2 name_3",
+        },
+    )
+
+    assert (
+        os.listdir(os.path.join(tmpdir, "foo_app")).sort() == ["requirements.txt", "main.py", "pages", "images"].sort()
+    )
+    assert (
+        os.listdir(os.path.join(tmpdir, "foo_app", "pages")).sort()
+        == ["name_1", "name_2", "name_3", "root.md", "root.py", "__init__.py"].sort()
+    )
+
+    oldpwd = os.getcwd()
+    os.chdir(os.path.join(tmpdir, "foo_app"))
+    stdout = _run_template("main.py")
+    os.chdir(oldpwd)
+    assert "[Taipy][INFO]  * Server starting on" in str(stdout, "utf-8")
+
+
+def test_multipage_gui_template_with_invalid_page_name(tmpdir, capfd):
+    with pytest.raises(FailedHookException):
+        cookiecutter(
+            template="src/taipy/templates/default",
+            output_dir=str(tmpdir),
+            no_input=True,
+            extra_context={
+                "Application root folder name": "foo_app",
+                "Page names in multi-page application?": "valid_var_name 1_invalid_var_name",
+            },
+        )
+
+    _, stderr = capfd.readouterr()
+    assert 'Page name "1_invalid_var_name" is not a valid Python identifier' in stderr
+
+    assert not os.path.exists(os.path.join(tmpdir, "foo_app"))

+ 92 - 0
src/taipy/templates/tests/test_scenario_mgt_template.py

@@ -0,0 +1,92 @@
+# Copyright 2023 Avaiga Private Limited
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+#        http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+# an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations under the License.
+
+import os
+
+from cookiecutter.main import cookiecutter
+
+from .utils import _run_template
+
+
+def test_scenario_management_with_toml_config(tmpdir):
+    cookiecutter(
+        template="src/taipy/templates/scenario-management",
+        output_dir=tmpdir,
+        no_input=True,
+        extra_context={
+            "Application root folder name": "foo_app",
+            "Application main Python file": "main.py",
+            "Application title": "bar",
+            "Does the application use TOML Config?": "yes",
+        },
+    )
+
+    assert os.listdir(tmpdir) == ["foo_app"]
+    assert (
+        os.listdir(os.path.join(tmpdir, "foo_app")).sort()
+        == ["requirements.txt", "main.py", "algos", "config", "pages"].sort()
+    )
+
+    # Assert post_gen_project hook is successful
+    with open(os.path.join(tmpdir, "foo_app", "requirements.txt")) as requirements_file:
+        assert "taipy==" in requirements_file.read()
+    assert (
+        os.listdir(os.path.join(tmpdir, "foo_app", "config")).sort()
+        == ["__init__.py", "config.py", "config.toml"].sort()
+    )
+    with open(os.path.join(tmpdir, "foo_app", "config", "config.py")) as config_file:
+        assert 'Config.load("config/config.toml")' in config_file.read()
+
+    oldpwd = os.getcwd()
+    os.chdir(os.path.join(tmpdir, "foo_app"))
+    stdout = _run_template("main.py")
+    os.chdir(oldpwd)
+
+    # Assert the message when the application is run successfully is in the stdout
+    assert "[Taipy][INFO] Configuration 'config/config.toml' successfully loaded." in str(stdout, "utf-8")
+    assert "[Taipy][INFO]  * Server starting on" in str(stdout, "utf-8")
+
+
+def test_scenario_management_without_toml_config(tmpdir):
+    cookiecutter(
+        template="src/taipy/templates/scenario-management",
+        output_dir=tmpdir,
+        no_input=True,
+        extra_context={
+            "Application root folder name": "foo_app",
+            "Application main Python file": "main.py",
+            "Application title": "bar",
+            "Does the application use TOML Config?": "no",
+        },
+    )
+
+    assert os.listdir(tmpdir) == ["foo_app"]
+    assert (
+        os.listdir(os.path.join(tmpdir, "foo_app")).sort()
+        == ["requirements.txt", "main.py", "algos", "config", "pages"].sort()
+    )
+
+    # Assert post_gen_project hook is successful
+    with open(os.path.join(tmpdir, "foo_app", "requirements.txt")) as requirements_file:
+        assert "taipy==" in requirements_file.read()
+    assert os.listdir(os.path.join(tmpdir, "foo_app", "config")).sort() == ["__init__.py", "config.py"].sort()
+    with open(os.path.join(tmpdir, "foo_app", "config", "config.py")) as config_file:
+        config_content = config_file.read()
+        assert 'Config.load("config/config.toml")' not in config_content
+        assert all([x in config_content for x in ["Config.configure_csv_data_node", "Config.configure_task"]])
+
+    oldpwd = os.getcwd()
+    os.chdir(os.path.join(tmpdir, "foo_app"))
+    stdout = _run_template("main.py")
+    os.chdir(oldpwd)
+
+    # Assert the message when the application is run successfully is in the stdout
+    assert "[Taipy][INFO]  * Server starting on" in str(stdout, "utf-8")

+ 18 - 0
src/taipy/templates/tests/utils.py

@@ -0,0 +1,18 @@
+import subprocess
+import sys
+
+
+def _run_template(main_path, time_out=30):
+    """Run the templates on a subprocess and get stdout after timeout"""
+    with subprocess.Popen([sys.executable, main_path], stdout=subprocess.PIPE, stderr=subprocess.PIPE) as proc:
+        try:
+            stdout, stderr = proc.communicate(timeout=time_out)
+        except subprocess.TimeoutExpired:
+            proc.kill()
+            stdout, stderr = proc.communicate()
+
+    # Print the eror if there is any (for debugging)
+    if stderr := str(stderr, "utf-8"):
+        print(stderr)
+
+    return stdout

+ 18 - 0
src/taipy/templates/tox.ini

@@ -0,0 +1,18 @@
+[tox]
+skipsdist = true
+isolated_build = true
+envlist = clean, lint
+
+[pytest]
+filterwarnings =
+    ignore::DeprecationWarning
+
+[testenv]
+allowlist_externals = pytest
+deps = pipenv==2023.7.23
+
+[testenv:tests]
+commands =
+    pipenv install --dev
+    pipenv run pip freeze
+    pytest tests

+ 1 - 0
src/taipy/templates/version.json

@@ -0,0 +1 @@
+{"major": 3, "minor": 1, "patch": 0, "ext": "dev0"}