From 46b33a622f9e58888a9d4da163a91887eed6efc3 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Tue, 26 Nov 2024 11:16:09 -0600 Subject: [PATCH] initial template for app based on wuttaweb with e.g. `poser install` command --- .gitignore | 2 + contrib/newapp.example.sh | 62 +++++++++++++++++++ cookiecutter.json | 12 ++++ hooks/pre_prompt.py | 8 +++ {{ cookiecutter.repo_name }}/README.md | 18 ++++++ {{ cookiecutter.repo_name }}/pyproject.toml | 51 +++++++++++++++ .../{{ cookiecutter.package_name }}/cli.py | 30 +++++++++ .../{{ cookiecutter.package_name }}/config.py | 29 +++++++++ .../db/__init__.py | 0 .../db/model/__init__.py | 9 +++ .../web/__init__.py | 0 .../web/app.py | 28 +++++++++ .../web/menus.py | 26 ++++++++ .../web/static/__init__.py | 22 +++++++ .../web/static/libcache/README | 2 + .../web/subscribers.py | 16 +++++ .../web/templates/base_meta.mako | 16 +++++ .../web/views/__init__.py | 13 ++++ 18 files changed, 344 insertions(+) create mode 100644 .gitignore create mode 100755 contrib/newapp.example.sh create mode 100644 cookiecutter.json create mode 100644 hooks/pre_prompt.py create mode 100644 {{ cookiecutter.repo_name }}/README.md create mode 100644 {{ cookiecutter.repo_name }}/pyproject.toml create mode 100644 {{ cookiecutter.repo_name }}/{{ cookiecutter.package_name }}/cli.py create mode 100644 {{ cookiecutter.repo_name }}/{{ cookiecutter.package_name }}/config.py create mode 100644 {{ cookiecutter.repo_name }}/{{ cookiecutter.package_name }}/db/__init__.py create mode 100644 {{ cookiecutter.repo_name }}/{{ cookiecutter.package_name }}/db/model/__init__.py create mode 100644 {{ cookiecutter.repo_name }}/{{ cookiecutter.package_name }}/web/__init__.py create mode 100644 {{ cookiecutter.repo_name }}/{{ cookiecutter.package_name }}/web/app.py create mode 100644 {{ cookiecutter.repo_name }}/{{ cookiecutter.package_name }}/web/menus.py create mode 100644 {{ cookiecutter.repo_name }}/{{ cookiecutter.package_name }}/web/static/__init__.py create mode 100644 {{ cookiecutter.repo_name }}/{{ cookiecutter.package_name }}/web/static/libcache/README create mode 100644 {{ cookiecutter.repo_name }}/{{ cookiecutter.package_name }}/web/subscribers.py create mode 100644 {{ cookiecutter.repo_name }}/{{ cookiecutter.package_name }}/web/templates/base_meta.mako create mode 100644 {{ cookiecutter.repo_name }}/{{ cookiecutter.package_name }}/web/views/__init__.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..df1440d --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +newapp.sh +newapp.sh~ diff --git a/contrib/newapp.example.sh b/contrib/newapp.example.sh new file mode 100755 index 0000000..5c5a40b --- /dev/null +++ b/contrib/newapp.example.sh @@ -0,0 +1,62 @@ +#!/bin/bash +################################################################################ +# +# auto rebuild 'playdough' app +# +# This script can be used to automatically re-create from scratch, a new +# app named 'playdough' - for sake of testing the cookiecutter template. +# +# IMPORTANT: To use this script, place a copy in the root of the +# cookiecutter template source folder (~/src/cookiecutter-wuttaweb) +# alongside the cookiecutter.json file. Name it `newapp.sh` and then +# modify your copy of the script as needed. (See TODO comments below.) +# +# The script does the following: +# - remove existing virtualenv and source code +# - drop and recreate database +# - make new virtualenv +# - install cookiecutter +# - make new source code via cookiecutter +# - install new app +# - run web app +# +################################################################################ + +set -e + +# TODO: adjust paths as needed +# nb. thanks to https://stackoverflow.com/a/246128 +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) +SRC_DIR=$( dirname -- "${SCRIPT_DIR}" ) +WORKON_HOME=/srv/envs + +# teardown +rm -rf $WORKON_HOME/playdough +rm -rf $SRC_DIR/playdough + +# database +sudo -u postgres dropdb --if-exists playdough +# TODO: adjust db owner as needed +sudo -u postgres createdb -O playdough playdough + +# virtualenv +python3 -m venv $WORKON_HOME/playdough +cd $WORKON_HOME/playdough +bin/pip install -U pip +bin/pip install -U setuptools wheel +bin/pip install -U cookiecutter + +# source +bin/cookiecutter --no-input -o $SRC_DIR $SCRIPT_DIR \ + full_name="Your Name" \ + email="you@example.com" \ + project_name="Playdough" \ + project_short_description="My web app" \ + open_source_license="Not open source" + +# install app +bin/pip install -e $SRC_DIR/playdough +bin/playdough install + +# serve web app +bin/pserve --reload file+ini:app/web.conf diff --git a/cookiecutter.json b/cookiecutter.json new file mode 100644 index 0000000..e8ebae4 --- /dev/null +++ b/cookiecutter.json @@ -0,0 +1,12 @@ +{ + "full_name": "Your Name", + "email": "you@example.com", + "project_name": "Playdough", + "distribution_name": "{{ cookiecutter.project_name.replace(' ', '-') }}", + "package_name": "{{ cookiecutter.project_name.lower().replace(' ', '_').replace('-', '_') }}", + "repo_name": "{{ cookiecutter.package_name }}", + "project_short_description": "My web app", + "open_source_license": ["MIT license", "BSD license", "ISC license", "Apache Software License 2.0", "GNU General Public License v3", "Not open source"], + "__egg_name": "{{ cookiecutter.distribution_name.replace('-', '-') }}", + "__studly_prefix": "{{ cookiecutter.project_name.replace(' ', '').replace('-', '') }}" +} diff --git a/hooks/pre_prompt.py b/hooks/pre_prompt.py new file mode 100644 index 0000000..1738016 --- /dev/null +++ b/hooks/pre_prompt.py @@ -0,0 +1,8 @@ + +import rich + +rich.print("\n\t[blue]Welcome to Wutta Framework[/blue]") +rich.print("\n\tThis tool will generate initial project code for a new web app.") +rich.print("\n\tYou will be asked questions about yourself (the author), and") +rich.print("\tthe project naming and license.") +print() diff --git a/{{ cookiecutter.repo_name }}/README.md b/{{ cookiecutter.repo_name }}/README.md new file mode 100644 index 0000000..98943b9 --- /dev/null +++ b/{{ cookiecutter.repo_name }}/README.md @@ -0,0 +1,18 @@ + +# {{cookiecutter.project_name}} + +This is a starter project based on `WuttaWeb +`_. + + +## Quick Start + +Make a virtual environment and install the app: + + python3 -m venv {{cookiecutter.repo_name}} + cd {{cookiecutter.repo_name}} + bin/pip install {{cookiecutter.distribution_name}} + bin/{{cookiecutter.package_name}} install + +For more info see +https://rattailproject.org/docs/wuttjamaican/narr/install/index.html diff --git a/{{ cookiecutter.repo_name }}/pyproject.toml b/{{ cookiecutter.repo_name }}/pyproject.toml new file mode 100644 index 0000000..b612549 --- /dev/null +++ b/{{ cookiecutter.repo_name }}/pyproject.toml @@ -0,0 +1,51 @@ + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "{{cookiecutter.project_name}}" +version = "0.1.0" +description = "{{cookiecutter.project_short_description}}" +readme = "README.md" +authors = [ + {name = "{{cookiecutter.full_name}}", email = "{{cookiecutter.email}}"} +] +maintainers = [ + {name = "{{cookiecutter.full_name}}", email = "{{cookiecutter.email}}"} +] +classifiers = [ + # TODO: remove this if you intend to publish your project + # (it's here by default, to prevent accidental publishing) + "Private :: Do Not Upload", +] +license = {text = "{{cookiecutter.open_source_license}}"} +dependencies = [ + "psycopg2", + "WuttaWeb", +] + +[project.scripts] +"{{cookiecutter.package_name}}" = "{{cookiecutter.package_name}}.cli:{{cookiecutter.package_name}}_typer" + +[project.entry-points."paste.app_factory"] +"main" = "{{cookiecutter.package_name}}.web.app:main" + +[project.entry-points."wutta.config.extensions"] +"{{cookiecutter.package_name}}" = "{{cookiecutter.package_name}}.config:{{cookiecutter.__studly_prefix}}Config" + + +# [project.urls] +# Homepage = "https://example.com/" +# Repository = "https://github.com/example/{{cookiecutter.repo_name}}" +# Issues = "https://github.com/example/{{cookiecutter.repo_name}}/issues" +# Changelog = "https://github.com/example/{{cookiecutter.repo_name}}/blob/master/CHANGELOG.md" + + +# [tool.commitizen] +# version_provider = "pep621" +# tag_format = "v$version" +# update_changelog_on_bump = true + +[tool.hatch.build.targets.wheel] +packages = ["{{cookiecutter.package_name}}"] diff --git a/{{ cookiecutter.repo_name }}/{{ cookiecutter.package_name }}/cli.py b/{{ cookiecutter.repo_name }}/{{ cookiecutter.package_name }}/cli.py new file mode 100644 index 0000000..4884d90 --- /dev/null +++ b/{{ cookiecutter.repo_name }}/{{ cookiecutter.package_name }}/cli.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8; -*- +""" +{{cookiecutter.project_name}} CLI +""" + +import typer + +from wuttjamaican.cli import make_typer + + +{{cookiecutter.package_name}}_typer = make_typer( + name='{{cookiecutter.package_name}}', + help="{{cookiecutter.project_name}} -- {{cookiecutter.project_short_description}}" +) + + +@{{cookiecutter.package_name}}_typer.command() +def install( + ctx: typer.Context, +): + """ + Install the {{cookiecutter.project_name}} app + """ + config = ctx.parent.wutta_config + app = config.get_app() + install = app.get_install_handler(pkg_name='{{cookiecutter.package_name}}', + app_title="{{cookiecutter.project_name}}", + pypi_name='{{cookiecutter.distribution_name}}', + egg_name='{{cookiecutter.__egg_name}}') + install.run() diff --git a/{{ cookiecutter.repo_name }}/{{ cookiecutter.package_name }}/config.py b/{{ cookiecutter.repo_name }}/{{ cookiecutter.package_name }}/config.py new file mode 100644 index 0000000..eae10d9 --- /dev/null +++ b/{{ cookiecutter.repo_name }}/{{ cookiecutter.package_name }}/config.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8; -*- +""" +{{cookiecutter.project_name}} config extensions +""" + +from wuttjamaican.conf import WuttaConfigExtension + + +class {{cookiecutter.__studly_prefix}}Config(WuttaConfigExtension): + """ + Config extension for {{cookiecutter.project_name}} + """ + key = '{{cookiecutter.package_name}}' + + def configure(self, config): + + # app info + config.setdefault(f'{config.appname}.app_title', "{{cookiecutter.project_name.replace('"', '\\"')}}") + config.setdefault(f'{config.appname}.app_dist', "{{cookiecutter.distribution_name}}") + + # app model + config.setdefault(f'{config.appname}.model_spec', '{{cookiecutter.package_name}}.db.model') + + # web app menu + config.setdefault(f'{config.appname}.web.menus.handler_spec', + '{{cookiecutter.package_name}}.web.menus:{{cookiecutter.__studly_prefix}}MenuHandler') + + # web app libcache + #config.setdefault('tailbone.static_libcache.module', '{{cookiecutter.package_name}}.web.static') diff --git a/{{ cookiecutter.repo_name }}/{{ cookiecutter.package_name }}/db/__init__.py b/{{ cookiecutter.repo_name }}/{{ cookiecutter.package_name }}/db/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/{{ cookiecutter.repo_name }}/{{ cookiecutter.package_name }}/db/model/__init__.py b/{{ cookiecutter.repo_name }}/{{ cookiecutter.package_name }}/db/model/__init__.py new file mode 100644 index 0000000..1c38c07 --- /dev/null +++ b/{{ cookiecutter.repo_name }}/{{ cookiecutter.package_name }}/db/model/__init__.py @@ -0,0 +1,9 @@ +# -*- coding: utf-8; -*- +""" +{{cookiecutter.project_name}} data models +""" + +# bring in all of wutta +from wuttjamaican.db.model import * + +# TODO: import other/custom models here... diff --git a/{{ cookiecutter.repo_name }}/{{ cookiecutter.package_name }}/web/__init__.py b/{{ cookiecutter.repo_name }}/{{ cookiecutter.package_name }}/web/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/{{ cookiecutter.repo_name }}/{{ cookiecutter.package_name }}/web/app.py b/{{ cookiecutter.repo_name }}/{{ cookiecutter.package_name }}/web/app.py new file mode 100644 index 0000000..06c32d9 --- /dev/null +++ b/{{ cookiecutter.repo_name }}/{{ cookiecutter.package_name }}/web/app.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8; -*- +""" +{{cookiecutter.project_name}} web app +""" + +from wuttaweb import app as base + + +def main(global_config, **settings): + """ + This function returns a Pyramid WSGI application. + """ + # prefer {{cookiecutter.project_name}} templates over wuttaweb + settings.setdefault('mako.directories', [ + '{{cookiecutter.package_name}}.web:templates', + 'wuttaweb:templates', + ]) + + # make config objects + wutta_config = base.make_wutta_config(settings) + pyramid_config = base.make_pyramid_config(settings) + + # bring in the rest of {{cookiecutter.project_name}} + pyramid_config.include('{{cookiecutter.package_name}}.web.static') + pyramid_config.include('{{cookiecutter.package_name}}.web.subscribers') + pyramid_config.include('{{cookiecutter.package_name}}.web.views') + + return pyramid_config.make_wsgi_app() diff --git a/{{ cookiecutter.repo_name }}/{{ cookiecutter.package_name }}/web/menus.py b/{{ cookiecutter.repo_name }}/{{ cookiecutter.package_name }}/web/menus.py new file mode 100644 index 0000000..4014eda --- /dev/null +++ b/{{ cookiecutter.repo_name }}/{{ cookiecutter.package_name }}/web/menus.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8; -*- +""" +{{cookiecutter.project_name}} Menu +""" + +from wuttaweb import menus as base + + +class {{cookiecutter.__studly_prefix}}MenuHandler(base.MenuHandler): + """ + {{cookiecutter.project_name}} menu handler + """ + + def make_menus(self, request, **kwargs): + + # TODO: override this if you need custom menus... + + # menus = [ + # self.make_products_menu(request), + # self.make_admin_menu(request), + # ] + + # ...but for now this uses default menus + menus = super().make_menus(request, **kwargs) + + return menus diff --git a/{{ cookiecutter.repo_name }}/{{ cookiecutter.package_name }}/web/static/__init__.py b/{{ cookiecutter.repo_name }}/{{ cookiecutter.package_name }}/web/static/__init__.py new file mode 100644 index 0000000..bdf1ced --- /dev/null +++ b/{{ cookiecutter.repo_name }}/{{ cookiecutter.package_name }}/web/static/__init__.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8; -*- +""" +Static assets +""" + +# from fanstatic import Library, Resource + + +# # libcache +# libcache = Library('{{cookiecutter.package_name}}_libcache', 'libcache') +# bb_vue_js = Resource(libcache, 'vue.esm-browser-3.3.11.prod.js') +# bb_oruga_js = Resource(libcache, 'oruga-0.8.10.js') +# bb_oruga_bulma_js = Resource(libcache, 'oruga-bulma-0.3.0.js') +# bb_oruga_bulma_css = Resource(libcache, 'oruga-bulma-0.3.0.css') +# bb_fontawesome_svg_core_js = Resource(libcache, 'fontawesome-svg-core-6.5.2.js') +# bb_free_solid_svg_icons_js = Resource(libcache, 'free-solid-svg-icons-6.5.2.js') +# bb_vue_fontawesome_js = Resource(libcache, 'vue-fontawesome-3.0.6.index.es.js') + + +def includeme(config): + config.include('wuttaweb.static') + config.add_static_view('{{cookiecutter.package_name}}', '{{cookiecutter.package_name}}.web:static', cache_max_age=3600) diff --git a/{{ cookiecutter.repo_name }}/{{ cookiecutter.package_name }}/web/static/libcache/README b/{{ cookiecutter.repo_name }}/{{ cookiecutter.package_name }}/web/static/libcache/README new file mode 100644 index 0000000..40e6029 --- /dev/null +++ b/{{ cookiecutter.repo_name }}/{{ cookiecutter.package_name }}/web/static/libcache/README @@ -0,0 +1,2 @@ +Place files in this folder, which correspond to the Resource() +definitions found in `{{cookiecutter.package_name}}/web/static/__init__.py` diff --git a/{{ cookiecutter.repo_name }}/{{ cookiecutter.package_name }}/web/subscribers.py b/{{ cookiecutter.repo_name }}/{{ cookiecutter.package_name }}/web/subscribers.py new file mode 100644 index 0000000..c75a50b --- /dev/null +++ b/{{ cookiecutter.repo_name }}/{{ cookiecutter.package_name }}/web/subscribers.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8; -*- +""" +Pyramid event subscribers +""" + +import {{cookiecutter.package_name}} + + +def add_{{cookiecutter.package_name}}_to_context(event): + renderer_globals = event + renderer_globals['{{cookiecutter.package_name}}'] = {{cookiecutter.package_name}} + + +def includeme(config): + config.include('wuttaweb.subscribers') + config.add_subscriber(add_{{cookiecutter.package_name}}_to_context, 'pyramid.events.BeforeRender') diff --git a/{{ cookiecutter.repo_name }}/{{ cookiecutter.package_name }}/web/templates/base_meta.mako b/{{ cookiecutter.repo_name }}/{{ cookiecutter.package_name }}/web/templates/base_meta.mako new file mode 100644 index 0000000..78c1d53 --- /dev/null +++ b/{{ cookiecutter.repo_name }}/{{ cookiecutter.package_name }}/web/templates/base_meta.mako @@ -0,0 +1,16 @@ +<%inherit file="wuttaweb:templates/base_meta.mako" /> + +## TODO: you can override parent template as needed below, or you +## can simply delete this file if no customizations are needed + +<%def name="favicon()"> + ${parent.favicon()} + + +<%def name="header_logo()"> + ${parent.header_logo()} + + +<%def name="footer()"> + ${parent.footer()} + diff --git a/{{ cookiecutter.repo_name }}/{{ cookiecutter.package_name }}/web/views/__init__.py b/{{ cookiecutter.repo_name }}/{{ cookiecutter.package_name }}/web/views/__init__.py new file mode 100644 index 0000000..d252a30 --- /dev/null +++ b/{{ cookiecutter.repo_name }}/{{ cookiecutter.package_name }}/web/views/__init__.py @@ -0,0 +1,13 @@ +# -*- coding: utf-8; -*- +""" +{{cookiecutter.project_name}} Views +""" + + +def includeme(config): + + # core views for wuttaweb + config.include('wuttaweb.views.essential') + + # TODO: include your own views here + #config.include('{{cookiecutter.package_name}}.web.views.widgets')