├── .gitignore ├── .gitlab-ci.yml ├── .pre-commit-config.yaml ├── LICENSE.txt ├── MANIFEST.in ├── Makefile ├── README.rst ├── assets └── nagare.js ├── conf └── assets.yaml ├── doc ├── Makefile ├── _static │ ├── distth___-webfont.eot │ ├── distth___-webfont.ttf │ ├── favicon.ico │ ├── logo.png │ └── theme.css ├── application_creation.txt ├── callbacks_forms.txt ├── changelog.txt ├── components.txt ├── conf.py ├── configuration_file.txt ├── database.txt ├── demo_installation.txt ├── demo_upgrade.txt ├── deployment.txt ├── description.txt ├── entry_points.txt ├── features.txt ├── framework_installation.txt ├── framework_upgrade.txt ├── i18n.txt ├── img │ ├── banner.png │ └── tutorial1.png ├── index.txt ├── license.txt ├── log.txt ├── mailing_lists.txt ├── make.bat ├── nagare-admin.txt ├── object_references.txt ├── presentation.txt ├── publisher_file.txt ├── quickstart.txt ├── renderer.txt ├── restful.txt ├── third_party.txt ├── tutorial1.txt ├── tutorial2.txt ├── tutorial3.txt ├── tutorial4.txt ├── wiki_tutorial.txt └── wsgiapp.txt ├── docker ├── Dockerfile └── pydistutils.cfg ├── entry-points.txt ├── pyproject.toml ├── src └── nagare │ ├── __init__.py │ ├── action.py │ ├── component.py │ ├── continuation.py │ ├── custom_build │ ├── __init__.py │ ├── backend.py │ └── build_assets.py │ ├── renderers │ ├── __init__.py │ ├── html.py │ ├── html5.py │ ├── xhtml.py │ └── xhtml5.py │ ├── server │ ├── __init__.py │ └── application.py │ ├── services │ ├── __init__.py │ ├── callbacks.py │ ├── core_exceptions.py │ ├── core_static.py │ ├── create_root.py │ ├── prg.py │ └── state.py │ └── state.py └── tests ├── test_component.py ├── test_html.py └── test_view.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.pyo 3 | *.orig 4 | *.rej 5 | *.egg-info 6 | *.db 7 | *.log 8 | *.mo 9 | .DS_Store 10 | .project 11 | .pydevproject 12 | .pytest_cache 13 | .ruff_cache 14 | .settings 15 | .idea 16 | .coverage 17 | .eggs 18 | *.swp 19 | /build 20 | /dist 21 | htmlcov 22 | nbproject 23 | /doc/_build 24 | .webassets-cache 25 | 26 | src/nagare/static 27 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | # -- 2 | # Copyright (c) 2008-2024 Net-ng. 3 | # All rights reserved. 4 | # 5 | # This software is licensed under the BSD License, as described in 6 | # the file LICENSE.txt, which you should have received as part of 7 | # this distribution. 8 | # -- 9 | 10 | image: docker-registry.net-ng.com/nng/dind-stackless:13 11 | 12 | tests: 13 | script: 14 | - /opt/stackless3/bin/pip install ruff black pytest . 15 | - /opt/stackless3/bin/ruff check src 16 | - /opt/stackless3/bin/black src 17 | - /opt/stackless3/bin/pytest 18 | 19 | pages: 20 | stage: deploy 21 | script: 22 | - /opt/stackless3/bin/pip install -e '.[doc]' 23 | # - /opt/stackless3/bin/sphinx-apidoc -s txt -T -o doc/source nagare 24 | - /opt/stackless3/bin/sphinx-build -b html doc public 25 | artifacts: 26 | paths: 27 | - public 28 | only: 29 | - master 30 | when: manual 31 | 32 | gh-pages: 33 | script: 34 | - /opt/stackless3/bin/pip install -e '.[doc]' 35 | # - /opt/stackless/bin/sphinx-apidoc -s txt -T -o doc/source nagare 36 | - git clone --branch gh-pages git@git.net-ng.com:nng/nagare-core.git gh-pages 37 | - /opt/stackless3/bin/sphinx-build -b html doc gh-pages 38 | - git config --global user.email "alain.poirier@net-ng.com" 39 | - git config --global user.name "Alain Poirier" 40 | - msg=`git log master -1 --pretty=short --abbrev-commit`; cd gh-pages; git add .; git commit -m "$msg" 41 | - git push git@git.net-ng.com:nng/nagare-core.git gh-pages 42 | when: manual 43 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: v4.6.0 4 | hooks: 5 | - id: trailing-whitespace 6 | - id: check-json 7 | - id: check-toml 8 | - id: check-yaml 9 | - id: check-added-large-files 10 | args: ["--maxkb=400"] 11 | 12 | - repo: https://github.com/charliermarsh/ruff-pre-commit 13 | # Ruff version. 14 | rev: "v0.4.4" 15 | hooks: 16 | - id: ruff 17 | files: "^src" 18 | - id: ruff-format 19 | files: "^src" 20 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2008-2024, Net-ng. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | - Redistributions of source code must retain the above copyright notice, 8 | this list of conditions and the following disclaimer. 9 | 10 | - Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | - Neither the name of Net-ng nor the names of its contributors may be used 15 | to endorse or promote products derived from this software without specific 16 | prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 21 | IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 22 | INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 23 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 25 | OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 26 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 27 | EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | prune doc 2 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: doc tests 2 | 3 | clean: 4 | @rm -rf build dist 5 | @rm -rf src/*.egg-info 6 | @find src \( -name '*.py[co]' -o -name '__pycache__' \) -delete 7 | @rm -rf doc/_build/* 8 | @rm -f src/nagare/static/js/nagare.js* 9 | 10 | upgrade-precommit: 11 | python -m pre_commit autoupdate 12 | 13 | install: clean 14 | python -m pip install -e '.[dev'] 15 | git init 16 | python -m pre_commit install 17 | 18 | webassets: 19 | python src/nagare/custom_build/build_assets.py 20 | 21 | tests: 22 | python -m pytest 23 | 24 | qa: 25 | python -m ruff check src 26 | python -m ruff format --check src 27 | $(MAKE) tests 28 | 29 | qa-fix: 30 | python -m ruff check --fix src 31 | python -m ruff format src 32 | 33 | doc: 34 | python -m sphinx.cmd.build -b html doc doc/_build 35 | 36 | wheel: 37 | python -m pip wheel -w dist --no-deps . 38 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | And Now For Something Completely Different 2 | ========================================== 3 | 4 | **Nagare** is an Open-Source framework, released under the BSD license, 5 | dedicated to rich web applications development in Python 3. 6 | 7 | **Nagare** doesn't follow the statu quo established by the classic frameworks 8 | of the Java, Python, Php and Ruby worlds: 9 | 10 | - no templating language 11 | - no explicit URL routing / mapping 12 | - no controllers proliferation 13 | - no manual management of the HTTP request / response cycles 14 | - no global session object 15 | - no REST by default 16 | 17 | When you forget all of that, what do you get? 18 | 19 | You get a powerful framework dedicated to web applications development, in 20 | opposition to simple content publishing. 21 | 22 | You get a truly component based framework where the application is a dynamic 23 | set of autonomous components, each one following its own independent control 24 | flow and maintaining its own private state. 25 | 26 | You get a framework where every web page is the composition of views from 27 | several components. 28 | 29 | You get a framework where the HTTP connectionless request / response cycle, 30 | the refresh, back and fork actions of the browser, don't break the normal 31 | control flow of the application. 32 | 33 | You get a framework where the web applications are developed in the same way 34 | as desktop applications. 35 | 36 | -------------------------------------------------------------------------------- /assets/nagare.js: -------------------------------------------------------------------------------- 1 | //-- 2 | // Copyright (c) 2008-2024, Net-ng. 3 | // All rights reserved. 4 | // 5 | // This software is licensed under the BSD License, as described in 6 | // the file LICENSE.txt, which you should have received as part of 7 | // this distribution. 8 | //-- 9 | 10 | "use strict"; 11 | 12 | class Nagare { 13 | constructor(error) { 14 | document.addEventListener('click', evt => this.processClick(evt), true); 15 | this.nagare_loaded_named_js = {}; 16 | 17 | if (error) this.error = error; 18 | } 19 | 20 | evalCSS(name, css, attrs) { 21 | if (css.length) { 22 | var style = document.createElement("style"); 23 | 24 | style.setAttribute("type", "text/css"); 25 | style.setAttribute("data-nagare-css", name); 26 | for (var name in attrs) style.setAttribute(name, attrs[name]); 27 | 28 | if (style.styleSheet) style.styleSheet.cssText = css; 29 | else style.appendChild(document.createTextNode(css)); 30 | 31 | document.head.appendChild(style); 32 | } 33 | } 34 | 35 | evalJS(name, js, attrs) { 36 | if (!this.nagare_loaded_named_js[name]) setTimeout(js, 0); 37 | this.nagare_loaded_named_js[name] = true; 38 | } 39 | 40 | fetchCSS(url, attrs) { 41 | var link = document.createElement("link"); 42 | 43 | link.setAttribute("rel", "stylesheet"); 44 | link.setAttribute("type", "text/css"); 45 | link.setAttribute("href", url); 46 | for (var name in attrs) link.setAttribute(name, attrs[name]); 47 | 48 | document.head.appendChild(link); 49 | } 50 | 51 | fetchJS(url, attrs) { 52 | var script = document.createElement("script"); 53 | 54 | script.setAttribute("type", "text/javascript"); 55 | script.setAttribute("src", url); 56 | for (var name in attrs) script.setAttribute(name, attrs[name]); 57 | 58 | document.head.appendChild(script); 59 | } 60 | 61 | loadAll(named_css, css, named_js, js) { 62 | for (var i = 0; i < named_css.length; i++) { 63 | var name = named_css[i][0]; 64 | var selector = "[data-nagare-css='" + name + "']"; 65 | if (!document.head.querySelector(selector)) this.evalCSS(name, named_css[i][1], named_css[i][2]); 66 | } 67 | 68 | for (var i = 0; i < named_js.length; i++) { 69 | var name = named_js[i][0]; 70 | var selector = "[data-nagare-js='" + name + "']"; 71 | if (!document.head.querySelector(selector)) this.evalJS(name, named_js[i][1], named_js[i][2]); 72 | } 73 | 74 | for (var i = 0; i < css.length; i++) { 75 | var url = css[i][0]; 76 | var a = document.createElement('a'); 77 | var links = document.head.querySelectorAll("link[rel=stylesheet]"); 78 | for (var j = 0, found = false; !found && j < links.length; j++) { 79 | a.href = links[j].href; 80 | found = (a.host == window.location.host) && (a.pathname == url); 81 | } 82 | if (!found) this.fetchCSS(url, css[i[1]]); 83 | } 84 | 85 | for (var i = 0; i < js.length; i++) { 86 | var url = js[i][0]; 87 | var selector = "script[src='" + url + "']"; 88 | if (!document.head.querySelector(selector)) this.fetchJS(url, css[i[1]]); 89 | } 90 | } 91 | 92 | replaceNode(id, html) { 93 | var node = document.getElementById(id); 94 | 95 | if (node && html) { 96 | var e = document.createElement(node.parentNode.tagName); 97 | e.innerHTML = html; 98 | var new_node = e.children[0]; 99 | 100 | new_node.querySelectorAll("script").forEach( 101 | js => { 102 | if (js.getAttribute("src")) { 103 | var script = document.createElement("script"); 104 | [...js.attributes].forEach(attr => script.setAttribute(attr.nodeName, attr.nodeValue)); 105 | js.parentNode.replaceChild(script, js); 106 | } else { 107 | js.parentNode.removeChild(js); 108 | setTimeout(js.textContent, 0); 109 | } 110 | } 111 | ); 112 | 113 | node.parentNode.replaceChild(new_node, node); 114 | } 115 | } 116 | 117 | error(status, text) { 118 | document.open(); 119 | document.write(text); 120 | document.close(); 121 | } 122 | 123 | sendRequest(url, options, params) { 124 | if (params && params.length) url += "&_params=" + encodeURIComponent(JSON.stringify(params)); 125 | 126 | options.cache = "no-cache"; 127 | options.headers = { "X-REQUESTED-WITH": "XMLHttpRequest" }; 128 | options.credentials = "same-origin"; 129 | 130 | return fetch(url, options) 131 | .catch(function () { throw new Error("Network error"); }) 132 | .then(function (response) { 133 | var status = response.status; 134 | 135 | if (!response.ok) { 136 | if (status === 503 && response.headers.get('location')) { 137 | window.document.location = response.headers.get('location'); 138 | } else { 139 | response.text().then(text => this.error(status, text)); 140 | } 141 | 142 | response = Promise.reject('Server error'); 143 | } 144 | 145 | return response; 146 | }.bind(this)); 147 | } 148 | 149 | callRemote(url) { 150 | return (...params) => this.sendRequest(url, { method: "GET" }, params).then(response => response.json()); 151 | } 152 | 153 | delay(url) { 154 | return (t, ...params) => new Promise(resolve => setTimeout(resolve, t, params)).then(args => this.callRemote(url)(...args)); 155 | } 156 | 157 | repeat(url) { 158 | class _repeat { 159 | constructor(t, url, args) { 160 | var interval = setInterval( 161 | function () { 162 | var p = nagare.callRemote(url)(...args).catch(function (e) { 163 | clearInterval(interval); 164 | throw e; 165 | }); 166 | 167 | if (this.then) p.then(this.then); 168 | if (this.catch) p.catch(this.catch); 169 | }.bind(this), 170 | t 171 | ); 172 | } 173 | 174 | then(f) { this.then = f; return this; } 175 | catch(f) { this.catch = f; return this; } 176 | } 177 | 178 | return (t, ...params) => new _repeat(t, url, params); 179 | } 180 | 181 | getField(field) { 182 | return encodeURIComponent(field.type === "checkbox" && !field.checked ? "" : field.value); 183 | } 184 | 185 | sendAndEval(url, options) { 186 | return this.sendRequest(url, options) 187 | .catch(Promise.reject) 188 | .then(response => response.text()) 189 | .then(eval) 190 | .catch(x => undefined); 191 | } 192 | 193 | getAndEval(url) { this.sendAndEval(url, { method: "GET" }) } 194 | 195 | postAndEval(form, action1, action2) { 196 | var data = new FormData(form); 197 | 198 | if (action1) data.append(action1[0], action1[1]); 199 | if (action2) data.append(action2[0], action2[1]); 200 | 201 | return this.sendAndEval("?", { method: "POST", body: data }); 202 | } 203 | 204 | processClick(event) { 205 | var target = event.target; 206 | if (!('nagare' in target.dataset)) { 207 | target = event.target.closest("a"); 208 | if (target) { 209 | if (!('nagare' in target.dataset)) { return true } 210 | } else { 211 | target = event.target.closest("button"); 212 | if (!target || !('nagare' in target.dataset)) { return true } 213 | } 214 | } 215 | 216 | switch (target.dataset['nagare'][1]) { 217 | case "5": 218 | var action = target.getAttribute("href"); 219 | this.getAndEval(action); 220 | break; 221 | 222 | case "6": 223 | var action = target.getAttribute("name"); 224 | this.postAndEval(target.form, [action, ""]); 225 | break; 226 | 227 | case "7": 228 | var action = target.getAttribute("name"); 229 | 230 | var offset = target.getBoundingClientRect(); 231 | var x = Math.round(event.clientX - offset.left); 232 | var y = Math.round(event.clientY - offset.top); 233 | 234 | this.postAndEval(target.form, [action + ".x", x], [action + ".y", y]); 235 | break; 236 | } 237 | 238 | event.preventDefault(); 239 | return false; 240 | } 241 | } 242 | 243 | var nagare = new Nagare(); 244 | -------------------------------------------------------------------------------- /conf/assets.yaml: -------------------------------------------------------------------------------- 1 | directory: .. 2 | bundles: 3 | nagare_js: 4 | filters: 5 | - rjsmin 6 | contents: 7 | - assets/nagare.js 8 | output: src/nagare/static/nagare.js 9 | -------------------------------------------------------------------------------- /doc/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = python -msphinx 7 | SPHINXPROJ = Nagare 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | api: 16 | python -msphinx.apidoc -o source -s txt -T ../nagare 17 | 18 | .PHONY: help Makefile 19 | 20 | # Catch-all target: route all unknown targets to Sphinx using the new 21 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 22 | %: Makefile 23 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 24 | -------------------------------------------------------------------------------- /doc/_static/distth___-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nagareproject/core/2791fe7b00ecb08e73362fa406dfd7807e00c209/doc/_static/distth___-webfont.eot -------------------------------------------------------------------------------- /doc/_static/distth___-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nagareproject/core/2791fe7b00ecb08e73362fa406dfd7807e00c209/doc/_static/distth___-webfont.ttf -------------------------------------------------------------------------------- /doc/_static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nagareproject/core/2791fe7b00ecb08e73362fa406dfd7807e00c209/doc/_static/favicon.ico -------------------------------------------------------------------------------- /doc/_static/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nagareproject/core/2791fe7b00ecb08e73362fa406dfd7807e00c209/doc/_static/logo.png -------------------------------------------------------------------------------- /doc/_static/theme.css: -------------------------------------------------------------------------------- 1 | @import "css/theme.css"; 2 | 3 | @font-face { 4 | font-family: 'DistrictThinRegular'; 5 | src: url('distth___-webfont.eot'); 6 | src: url('distth___-webfont.eot?iefix') format('eot'), 7 | url('distth___-webfont.ttf') format('truetype'); 8 | font-weight: normal; 9 | font-style: normal; 10 | } 11 | 12 | body { color: #3a3a3a; } 13 | 14 | body, th, tr { 15 | font-family: arial, verdana, sans, geneva; 16 | font-size: 17px; 17 | } 18 | 19 | .wy-nav-content-wrap { background-color: #f8f8f8; } 20 | 21 | .wy-nav-top { 22 | background-color: #e3e3e3; 23 | color: #3f7516; 24 | } 25 | .wy-nav-top a { 26 | font: bold 30px "DistrictThinRegular"; 27 | color: #3f7516; 28 | } 29 | 30 | .wy-side-nav-search { background-color: #e3e3e3; } 31 | .wy-side-nav-search input[type="text"] { border-color: #3f7516; } 32 | .wy-side-nav-search, .wy-side-nav-search > a, .wy-side-nav-search img { padding-top: 0; } 33 | 34 | .wy-menu-vertical li.current > a { background-color: white } 35 | 36 | .wy-nav-content div[aria-label="breadcrumbs navigation"] { display: None } 37 | 38 | .wy-nav-content { 39 | max-width: 960px; 40 | background-color: white; 41 | margin: 0; 42 | } 43 | 44 | p { 45 | margin-bottom: 15px; 46 | text-align: justify; 47 | font-size: 17px; 48 | } 49 | 50 | em { font-style: italic } 51 | 52 | blockquote { margin: 0 0 15px 0 } 53 | 54 | .htmlscreen { margin-bottom: 15px } 55 | 56 | h1 { 57 | color: #3a3a3a; 58 | border: 0; 59 | font: bold 34px "DistrictThinRegular"; 60 | } 61 | 62 | h2 { 63 | margin: 25px 0 15px 0; 64 | color: #3f7516; 65 | border-bottom: 1px solid #9f9f9f; 66 | font-size: 26px; 67 | } 68 | 69 | h3 { 70 | margin: 20px 0 15px 0; 71 | font-size: 23px; 72 | } 73 | 74 | h4 { 75 | margin: 20px 0 15px 0; 76 | font-style: italic; 77 | font-size: 20px; 78 | } 79 | 80 | h2, h3, h4, .rst-content .toctree-wrapper p.caption { 81 | font-family: Arial, Verdana, 'Bitstream Vera Sans', Helvetica, sans-serif; 82 | font-weight: normal; 83 | } 84 | 85 | .rst-content .section ul li { list-style-type: square } 86 | .rst-content .section ol.arabic li ul li { list-style-type: square } 87 | .rst-content ol.arabic li { margin-left: 40px } 88 | 89 | .rst-content a { 90 | color: #3f7516; 91 | border-bottom: 1px dotted #3f7516; 92 | } 93 | 94 | .rst-content blockquote { margin: 0; } 95 | .rst-content .highlight > pre, .rst-content .linenodiv > pre { line-height: 1.5 } 96 | 97 | div[class^="highlight"] { 98 | margin: 0 0 15px 24px; 99 | background-color: #f8f8f8; 100 | background-repeat: no-repeat; 101 | border: 0px; 102 | border-left-width: 2px; 103 | border-left-style: solid; 104 | } 105 | 106 | div[class^="highlight"] div.highlight { 107 | background: None; 108 | border: 0; 109 | margin: 0; 110 | padding: 0; 111 | } 112 | 113 | div[class^="highlight"] pre { font-size: 14px } 114 | 115 | .note div[class^="highlight"] { background: none; } 116 | 117 | .wy-table-responsive table td { white-space: normal; } 118 | 119 | .rst-content table.docutils { 120 | width: 100%; 121 | border: 1px solid #3f7516; 122 | border-collapse: collapse; 123 | } 124 | 125 | .rst-content table.docutils:not(.field-list) tr:nth-child(2n-1) td { background: none } 126 | .rst-content table.docutils:not(.field-list) tr:nth-child(2n) td { background: #edfae1 } 127 | .rst-content table.docutils thead th { background: #d4e2c8; } 128 | 129 | div.description-left { float: left; } 130 | div.description-right { 131 | padding-left: 20px; 132 | overflow: hidden; 133 | } 134 | 135 | @media print { 136 | .wy-nav-content { max-width: none } 137 | body, th, tr { font-size: 14px; } 138 | p { 139 | font-size: 14px; 140 | margin-bottom: 6px; 141 | line-height: normal 142 | } 143 | h1 { font-size: 28px } 144 | h2 { font-size: 22px; } 145 | h3 { font-size: 20; } 146 | h4 { font-size: 17px; } 147 | div[class^="highlight"] pre { font-size: 13px } 148 | } 149 | 150 | 151 | -------------------------------------------------------------------------------- /doc/application_creation.txt: -------------------------------------------------------------------------------- 1 | Create an application 2 | ===================== 3 | 4 | This document describes how to create an application using the Nagare framework. 5 | 6 | In the following chapters, we will create the application named *my_app*. 7 | 8 | 1. Create the application skeleton 9 | ---------------------------------- 10 | 11 | In a directory of your choice, create the application skeleton: 12 | 13 | .. code-block:: sh 14 | 15 | /bin/nagare-admin create-app my_app 16 | 17 | This command creates a *my_app* directory with the structure: 18 | 19 | - ``setup.py`` -- setuptools configuration file 20 | - ``conf`` -- directory with all the configuration files of the application 21 | - ``data`` -- data directory, contains all the read/write data (database files for example) used by the application 22 | - ``static`` -- static directory, contains all the read only data (images, css, js...) used by the application 23 | - ``my_app`` -- source directory, contains the application source files 24 | 25 | 2. Configure your application 26 | ----------------------------- 27 | 28 | 2.1. File setup.py 29 | ~~~~~~~~~~~~~~~~~~ 30 | 31 | In your application directory, edit the ``setup.py`` file: 32 | 33 | .. code-block:: python 34 | 35 | VERSION = '0.0.1' 36 | 37 | from setuptools import setup, find_packages 38 | 39 | setup( 40 | name = 'my_app', 41 | version = VERSION, 42 | author = '', 43 | author_email = '', 44 | description = '', 45 | license = '', 46 | keywords = '', 47 | url = '', 48 | packages = find_packages(), 49 | include_package_data = True, 50 | package_data = {'' : ['*.cfg']}, 51 | zip_safe = False, 52 | install_requires = ('nagare',), 53 | entry_points = """ 54 | [nagare.applications] 55 | my_app = my_app.app:app 56 | """ 57 | ) 58 | 59 | Change the keywords of the ``setup()`` call (ie: author = 'John Doe', description = 60 | 'This is my first application'...) to correctly describe your application. 61 | 62 | Note that the ``install_requires`` keyword initially list the ``nagare`` package. 63 | Such that, if someone wants to install you application in a system where 64 | Nagare is not currently installed, it will be automatically downloaded and 65 | installed. 66 | 67 | The available keywords are described into the 68 | `Packaging and Distributing Projects `_ 69 | documentation. 70 | 71 | 2.2. Application configuration file 72 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 73 | 74 | Edit the ``conf/my_app.cfg`` file: 75 | 76 | .. code-block:: ini 77 | 78 | [application] 79 | path = app my_app 80 | name = my_app 81 | debug = off 82 | 83 | [database] 84 | activated = off 85 | uri = sqlite:///$here/../data/my_app.db 86 | metadata = my_app.models:__metadata__ 87 | debug = off 88 | 89 | .. note:: 90 | 91 | The structure and informations in this file are described with details in 92 | :doc:`configuration_file`. 93 | 94 | Briefly, the **[application]** section contains: 95 | 96 | - ``path`` -- factory component reference, used by the framework to create the root component of the application 97 | - ``name`` -- the url of the application (i.e http://localhost:8080/my_app) 98 | - ``debug`` -- debug mode to (de)activate the web error page 99 | 100 | and the **[database]** section contains: 101 | 102 | - ``activated`` -- indicate if the application uses a database 103 | - ``uri`` -- database URI for SQLAlchemy 104 | - ``metadata`` -- reference to the SQLAlchemy metadata object 105 | - ``debug`` -- debug mode to (de)activate the display of the SQL requests 106 | 107 | 3. Register your application to the Nagare framework 108 | ---------------------------------------------------- 109 | 110 | As the default generated ``setup.py`` declares a ``nagare.applications`` entry point, 111 | once installed, the application is automatically registered to Nagare. 112 | 113 | So to register your application, go into the ``my_apps`` directory and simply 114 | enter the command: 115 | 116 | .. code-block:: sh 117 | 118 | /bin/python setup.py develop 119 | 120 | .. note:: 121 | 122 | The entry points you can declare in the ``setup.py`` file are described 123 | in :doc:`entry_points`. 124 | 125 | You can check your application is correctly registered by launching the command: 126 | 127 | .. code-block:: sh 128 | 129 | /bin/nagare-admin serve 130 | 131 | which list all the applications known by the framework. 132 | 133 | 4. Create the database 134 | ---------------------- 135 | 136 | .. note:: 137 | 138 | The document :doc:`database` describes how to set and use a relational 139 | database with the framework. 140 | 141 | If your application uses a database you will need to create the database tables 142 | prior to launch the application: 143 | 144 | .. code-block:: sh 145 | 146 | /bin/nagare-admin create-db my_app 147 | 148 | The options available with the ``create-db`` administrative command are: 149 | 150 | -d, --debug to display of the generated SQL requests 151 | --drop drop the tables if they exist, prior to re-create them 152 | --no-populate don't call the populate function after the tables creation 153 | 154 | The database tables can be removed with the command: 155 | 156 | .. code-block:: sh 157 | 158 | /bin/nagare-admin drop-db my_app 159 | 160 | 5. Launch your application 161 | -------------------------- 162 | 163 | The following command launches the application: 164 | 165 | .. code-block:: sh 166 | 167 | /bin/nagare-admin serve my_app 168 | 169 | which becomes available at http://localhost:8080/my_app 170 | 171 | These options are interesting when you are in development mode: 172 | 173 | -d, --debug display a specialized web error page when an exception occur 174 | --reload automatically reload the application when one of its source file is changed 175 | 176 | .. note:: 177 | 178 | The :doc:`nagare-admin` guide lists the several administrative 179 | commands with all their options 180 | 181 | 6. Distribute your application 182 | ------------------------------ 183 | 184 | When your application is ready to be released, you can create a source 185 | distribution (tarball on Unix, ZIP file on Windows) with the command (into the 186 | ``my_apps`` directory): 187 | 188 | .. code-block:: sh 189 | 190 | /bin/python setup.py sdist 191 | 192 | The source distribution is created into the ``dist`` directory. 193 | 194 | If you want to globally distribute your application, you can 195 | register on the central `PyPI `_ repository and 196 | `uploaded `_ it there. 197 | 198 | You can also create a binary distribution (ie. a *wheel*) or a Windows 199 | installer for example, as explain in `Packaging your Project `_ 200 | 201 | -------------------------------------------------------------------------------- /doc/changelog.txt: -------------------------------------------------------------------------------- 1 | Nagare Changelog 2 | ================ 3 | 4 | 0.5.0 5 | ----- 6 | 7 | - Project only hosted on Github now 8 | - Documentation served as github.io pages 9 | - Documentation generated with Sphinx 10 | - Documentations added: 11 | 12 | - internationalization 13 | - ``nagare.wsgi.WSGIApp`` 14 | - application deployment 15 | - log service 16 | 17 | - Running now on Stackless, Pypy (experimental) and CPython (with limitations) 18 | - Request and Response object factories added 19 | - Improved components garbage collector 20 | - Callbacks registered with ``.action()`` and ``.answer()`` can have parameters and keywords 21 | - No more ``lambda`` for the validators 22 | (``lambda v: IntValidator(v).greater_than(10)`` => ``IntValidator().greater_than(10)``) 23 | - Translation *domain* added to the i18n service API 24 | - Custom ``data-*`` HTML5 attributes can be created as ``data_*`` keywords 25 | - The ``
`` automatically added in the asynchronous views have the 26 | ``nagare-generated`` and ``nagare-async-view`` classes 27 | 28 | 0.4.1 29 | ----- 30 | 31 | Bugs fixed 32 | ~~~~~~~~~~ 33 | 34 | - #1516: `nagare-admin create-app` command not working with Stackless Python < 2.7 35 | 36 | 37 | 0.4.0 38 | ----- 39 | 40 | New features 41 | ~~~~~~~~~~~~ 42 | 43 | - Mercurial migration 44 | - new site, examples and documentation design 45 | - tutorial 46 | - i18n service (messages catalogs, HTTP language negotiation, timezones, dates, currencies, numbers ...) 47 | - multiprocess / thread scopes differences handled by the new :mod:`nagare.local` service 48 | - HTML5 renderer (:class:`nagare.namespaces.xhtml5.Renderer`) 49 | - user impersonnalisation (stackable user objects) 50 | - stackable security rules 51 | - standalone publisher threads pool fully configurable 52 | - dummy sessions manager for completly Restful applications (no state kept on the server) 53 | - fastcgi over unix socket 54 | - the DOCTYPE can be changed 55 | - the content type of an XML response can be changed 56 | - configurable default initial renderer 57 | - relative logger names 58 | - ``nagare-admin shell`` prompt displays the name of the application 59 | - application name can be inserted as ``$name`` in the configuration file 60 | 61 | Changes 62 | ~~~~~~~ 63 | 64 | - A renderer can create an asynchronous renderer: 65 | ``comp.render(xhtml.AsyncRenderer())`` => ``comp.render(h.AsyncRenderer())`` 66 | - New versions of YUI, IPython, SQLAlchemy, lxml and WebOb 67 | 68 | Bugs fixed 69 | ~~~~~~~~~~ 70 | 71 | - client disconnection detection added for comet streams 72 | - #51: javascript executed multiple times 73 | - #52: more informations for the ``AnswerWithoutCall`` exception 74 | - #53: a ``component.Task`` must exit on ``answer()`` if a ``on_answer()`` was set 75 | - #54: log service not initialized 76 | - #55: xhtml ``select`` and ``option``: problem with integer 77 | - #66: serializer can't serialize an empty DOM 78 | - #67: serializer can't serialize a fragment (a list of DOM) 79 | - #1298: bad generation of pure ``href`` anchor 80 | - #1442: bad Javascript translation of Python methods 81 | - #1443: error when the default initial renderer is asynchronous 82 | - #1444: ajax error when no HTML is rendered 83 | - #1447: ``IntValidator`` does not catch ``TypeError`` 84 | - #1498: tutorial errors 85 | - #1502: incorrect ``memory`` sessions manager 86 | - #1508: error with the latest IPython version 87 | - #1509: ``data_path`` not initialized 88 | - #1510: bad checkbox values in ajax 89 | - #1511: SQLAlchemy engines creation fully configurable 90 | 91 | 92 | 0.3.0 93 | ----- 94 | 95 | New features 96 | ~~~~~~~~~~~~ 97 | 98 | - refactoring of the sessions managers: 99 | 100 | - session objects now keep track of their sessions manager 101 | - no more sessions manager factories 102 | - configurable pickler / unpickler objects 103 | - configuration switch ``states_history`` to set if an objects graphs history must be kept 104 | - new sessions manager (``type=memory``) that keeps the objects graphs in memory, without any pickling 105 | - logging service added: 106 | 107 | - one dedicated logger for each published applications is created 108 | - easy configuration and use of this dedicated logger 109 | - all the ``[logging]`` sections of all the published applications are merged before to configure the Python logging system 110 | - preliminary Comet support added (currently only working in a multi-threaded env.) 111 | - last exception raised kept by the ``WSGIApp`` objects and exception hook added 112 | - ``with_request`` parameter added to all the callback registrations and ``Update()`` objects 113 | - translation of Python modules to Javascript added 114 | - configurable name for the security cookie (one of the post Nagare security audit actions) 115 | - configuration of the ``WSGIApp`` objects split accross multiples `set_*`` methods 116 | - ``get_registered_applications()`` added to the publisher objects 117 | - full YUI bundled with Nagare 118 | - New versions: 119 | 120 | - Stackless Python 2.6.4 is now the recommanded Python version 121 | - virtualenv updated to 1.4.5 122 | - SQLAlchemy updated to 0.5.8 123 | - Elixir updated to 0.7.1 124 | - Lxml updated to 2.2.4 125 | - YUI updated to 2.8.0r4 126 | 127 | Changes 128 | ~~~~~~~ 129 | 130 | - with the YUI connection manager, a large browser response must be reassembled (Firefox only) 131 | - late creation of the SQLAlchemy database engines and metadatas binding 132 | - input fields ot type ``button`` now working in an Ajax submit 133 | - ``Var.var()`` now working in a unicode context 134 | - ``nagare-admin create-rules`` had problems when a static directory didn't exist 135 | - bad boolean expressions parenthesis translation in pyjs fixed 136 | - parsing (X)HTML from URL now working under Windows 137 | 138 | Bugs fixed 139 | ~~~~~~~~~~ 140 | 141 | - #47: ``set_publisher()`` called when using "nagare-admin create-db" 142 | - #48: py2js parentheses bug 143 | - #49: ``reset`` configuration ignored by the memcached sessions manager 144 | - #50: [log] inferred caller is wrong 145 | 146 | 147 | 0.2.0 148 | ----- 149 | 150 | Python Stackless 2.6.2 is now the recommanded Python version. 151 | 152 | New features 153 | ~~~~~~~~~~~~ 154 | 155 | - When an AJAX update contains CSS or Javascript urls, they are correctly fetched. 156 | - Multiple AJAX updates object added 157 | - Session lock added (distributed lock when memcached is used) 158 | - A session can now contains SQLAlchemy (and Elixir) entities 159 | - LRU management of the sessions and continuations 160 | - ``nagare-admin create-rules`` administrative command added. 161 | Generation of the Apache / lighttpd / ngnix rewrite rules to serve the statics 162 | contents. See :doc:`nagare-admin` 163 | - ``nagare-admin batch`` administrative command added. To execute Python 164 | statements. See :doc:`nagare-admin` 165 | - Easy WSGI pipe creation 166 | - An application can now be registered under several urls 167 | - The automatic reloader can be configured with a list of files to watch 168 | - API to logout and change the user identity/password added 169 | - automatic generation of a ``link(rel="canonical" ...)`` in the page header 170 | as an alias without the session and continuation parameters 171 | - ``min_compress_len`` parameter added in the memcached configuration 172 | - YUI AJAX modules updated to 2.7.0 173 | - SQLAlchemy updated to 0.5.x 174 | 175 | Changes 176 | ~~~~~~~ 177 | 178 | - Complete refactoring of the AJAX communication. The "wire" format is now Javascript. 179 | - ``component.Component.init()`` and ``presentation.init_for()`` API changes. 180 | See :doc:`restful` 181 | 182 | Bugs fixed 183 | ~~~~~~~~~~ 184 | 185 | - #19, #23, #26: race condition in the sessions management 186 | - #22: don't clear the registered callbacks when an image is served 187 | - #21: set the security context at the beginning of the request handling 188 | - #13, #14: python to javascript translation updated 189 | 190 | 191 | 0.1.0 192 | ----- 193 | 194 | Initial release 195 | 196 | -------------------------------------------------------------------------------- /doc/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Nagare documentation build configuration file, created by 4 | # sphinx-quickstart on Fri Sep 29 15:07:51 2017. 5 | # 6 | # This file is execfile()d with the current directory set to its 7 | # containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | 15 | # If extensions (or modules to document with autodoc) are in another directory, 16 | # add these directories to sys.path here. If the directory is relative to the 17 | # documentation root, use os.path.abspath to make it absolute, like shown here. 18 | # 19 | #import os 20 | #import sys 21 | #sys.path.insert(0, os.path.abspath('.')) 22 | 23 | 24 | # -- General configuration ------------------------------------------------ 25 | 26 | # If your documentation needs a minimal Sphinx version, state it here. 27 | # 28 | # needs_sphinx = '1.0' 29 | 30 | # Add any Sphinx extension module names here, as strings. They can be 31 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 32 | # ones. 33 | extensions = ['sphinx.ext.autodoc', 'sphinx.ext.githubpages', 'sphinxcontrib.mermaid'] 34 | 35 | # Add any paths that contain templates here, relative to this directory. 36 | templates_path = ['_templates'] 37 | 38 | # The suffix(es) of source filenames. 39 | # You can specify multiple suffix as a list of string: 40 | # 41 | # source_suffix = ['.rst', '.md'] 42 | source_suffix = '.txt' 43 | 44 | # The master toctree document. 45 | master_doc = 'index' 46 | 47 | # General information about the project. 48 | project = u'Nagare' 49 | copyright = u'2017, Net-ng' 50 | author = u'Alain Poirier' 51 | 52 | # The version info for the project you're documenting, acts as replacement for 53 | # |version| and |release|, also used in various other places throughout the 54 | # built documents. 55 | # 56 | # The short X.Y version. 57 | version = u'0.5' 58 | # The full version, including alpha/beta/rc tags. 59 | release = u'0.5.0' 60 | 61 | # The language for content autogenerated by Sphinx. Refer to documentation 62 | # for a list of supported languages. 63 | # 64 | # This is also used if you do content translation via gettext catalogs. 65 | # Usually you set "language" from the command line for these cases. 66 | language = None 67 | 68 | # List of patterns, relative to source directory, that match files and 69 | # directories to ignore when looking for source files. 70 | # This patterns also effect to html_static_path and html_extra_path 71 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 72 | 73 | # The name of the Pygments (syntax highlighting) style to use. 74 | #pygments_style = 'trac' 75 | 76 | # If true, `todo` and `todoList` produce output, else they produce nothing. 77 | todo_include_todos = False 78 | 79 | 80 | # -- Options for HTML output ---------------------------------------------- 81 | 82 | # The theme to use for HTML and HTML Help pages. See the documentation for 83 | # a list of builtin themes. 84 | # 85 | html_theme = 'sphinx_rtd_theme' 86 | html_style = 'theme.css' 87 | 88 | html_favicon = '_static/favicon.ico' 89 | html_logo = '_static/logo.png' 90 | 91 | # Theme options are theme-specific and customize the look and feel of a theme 92 | # further. For a list of options available for each theme, see the 93 | # documentation. 94 | html_theme_options = dict( 95 | logo_only=True, 96 | collapse_navigation=True, 97 | prev_next_buttons_location='bottom', 98 | display_version=False, 99 | sticky_navigation=False 100 | ) 101 | 102 | # Add any paths that contain custom static files (such as style sheets) here, 103 | # relative to this directory. They are copied after the builtin static files, 104 | # so a file named "default.css" will overwrite the builtin "default.css". 105 | html_static_path = ['_static'] 106 | 107 | # Custom sidebar templates, must be a dictionary that maps document names 108 | # to template names. 109 | # 110 | # This is required for the alabaster theme 111 | # refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars 112 | #html_sidebars = { 113 | # '**': [ 114 | # 'about.html', 115 | # 'navigation.html', 116 | # 'relations.html', # needs 'show_related': True theme option to display 117 | # 'searchbox.html', 118 | # 'donate.html', 119 | # ] 120 | #} 121 | 122 | html_show_copyright = False 123 | html_show_sphinx = False 124 | #html_use_index = False 125 | html_show_sourcelink = False 126 | 127 | # -- Options for HTMLHelp output ------------------------------------------ 128 | 129 | # Output file base name for HTML help builder. 130 | htmlhelp_basename = 'Nagaredoc' 131 | 132 | 133 | # -- Options for LaTeX output --------------------------------------------- 134 | 135 | latex_elements = { 136 | # The paper size ('letterpaper' or 'a4paper'). 137 | # 138 | # 'papersize': 'letterpaper', 139 | 140 | # The font size ('10pt', '11pt' or '12pt'). 141 | # 142 | # 'pointsize': '10pt', 143 | 144 | # Additional stuff for the LaTeX preamble. 145 | # 146 | # 'preamble': '', 147 | 148 | # Latex figure (float) alignment 149 | # 150 | # 'figure_align': 'htbp', 151 | } 152 | 153 | # Grouping the document tree into LaTeX files. List of tuples 154 | # (source start file, target name, title, 155 | # author, documentclass [howto, manual, or own class]). 156 | latex_documents = [ 157 | (master_doc, 'Nagare.tex', u'Nagare Documentation', 158 | u'Alain Poirier', 'manual'), 159 | ] 160 | 161 | 162 | # -- Options for manual page output --------------------------------------- 163 | 164 | # One entry per manual page. List of tuples 165 | # (source start file, name, description, authors, manual section). 166 | man_pages = [ 167 | (master_doc, 'nagare', u'Nagare Documentation', 168 | [author], 1) 169 | ] 170 | 171 | 172 | # -- Options for Texinfo output ------------------------------------------- 173 | 174 | # Grouping the document tree into Texinfo files. List of tuples 175 | # (source start file, target name, title, author, 176 | # dir menu entry, description, category) 177 | texinfo_documents = [ 178 | (master_doc, 'Nagare', u'Nagare Documentation', 179 | author, 'Nagare', 'One line description of project.', 180 | 'Miscellaneous'), 181 | ] 182 | 183 | 184 | 185 | -------------------------------------------------------------------------------- /doc/configuration_file.txt: -------------------------------------------------------------------------------- 1 | The application configuration file 2 | ================================== 3 | 4 | Using the configuration file 5 | ---------------------------- 6 | 7 | The ``serve`` administrative command needs a proper configuration file to 8 | launch an application. 9 | 10 | You can directly give the configuration file to the ``serve`` command, as: 11 | 12 | .. code-block:: sh 13 | 14 | /bin/nagare-admin serve /path/to/the/config.cfg 15 | 16 | Or, if the application was registered to the framework 17 | (as described in :doc:`entry_points`), then you can launch it with: 18 | 19 | .. code-block:: sh 20 | 21 | /bin/nagare-admin serve 22 | 23 | In this case, the framework reads the configuration file named ``.cfg`` 24 | in the directory ``*/conf`` under 25 | ``/lib/python2.7/site-packages``. 26 | 27 | .. note:: 28 | 29 | If an application is launched in debug mode, changes in the configuration 30 | file is immediatly reflected. Else, you need to stop then re-launch the 31 | application. 32 | 33 | Structure 34 | --------- 35 | 36 | For the boolean parameters, a value of ``true``, ``yes``, ``on`` or ``1`` means True 37 | and a value of ``false``, ``no``, ``off`` or ``0`` mean False. 38 | 39 | You can use the ``$here`` variable which contains the path to the directory where 40 | the configuration file is located. 41 | 42 | Comments, starting with the ``#`` character can be added to a configuration file. 43 | 44 | [application] section 45 | ~~~~~~~~~~~~~~~~~~~~~ 46 | 47 | =================== ========= ================== ================================================ 48 | Name Mandatory Default value Description 49 | =================== ========= ================== ================================================ 50 | path Yes *No default value* Reference to the root component factory of the 51 | application (see :doc:`object_references`) 52 | name Yes *No default value* ``/name`` will be the URL of the application 53 | static No ``static`` Filesystem path to the static contents of 54 | directory the application. By default, it's the 55 | ``static`` directory under the application 56 | installation directory. 57 | always_html No yes If this parameter is false, the framework will 58 | send XHTML to the browsers that accept XHTML, 59 | else HTML. If this parameter is true, HTML is 60 | always generated 61 | debug No no Display the web debug page when an exception 62 | occurs. The ``nagare[debug]`` extra must be installed. 63 | =================== ========= ================== ================================================ 64 | 65 | [database] section 66 | ~~~~~~~~~~~~~~~~~~ 67 | 68 | =================== ========= ================== ================================================ 69 | Name Mandatory Default value Description 70 | =================== ========= ================== ================================================ 71 | activated No off If not activated, the framework will not read 72 | the following parameters 73 | uri Yes *No default value* URI or connection string to the database, 74 | as described in the SQLAlchemy `engine configuration `_ 75 | metadata Yes *No default value* Reference to the SQLAlchemy metadata object 76 | (see :doc:`object_references`) 77 | populate No *No default value* Reference to an optional function, called 78 | after the table creation to populate them 79 | with some initial data (see :doc:`object_references`) 80 | debug No off Display the generated SQL requests 81 | =================== ========= ================== ================================================ 82 | 83 | All other parameters, if present, are passed as keywords to the SQLALchemy 84 | ``create_engine()`` call (see http://www.sqlalchemy.org/docs/core/engines.html#engine-creation-api) 85 | 86 | If an application needs to work with several database, several subsections can 87 | be embedded into the main ``[database]`` section: 88 | 89 | .. code-block:: ini 90 | 91 | [database] 92 | 93 | [[database1]] # The name of a subsection is irrelevant but must be unique 94 | activated = on 95 | uri = ... 96 | metadata = ... 97 | 98 | [[database2]] 99 | activated = on 100 | uri = ... 101 | metadata = ... 102 | 103 | -------------------------------------------------------------------------------- /doc/database.txt: -------------------------------------------------------------------------------- 1 | Database tier 2 | ============= 3 | 4 | You can use `SQLAlchemy `_ or its declarative 5 | layer Elixir with Nagare. For each request received, Nagare creates a 6 | database transaction which is commited at the end 7 | of the request handling or rollbacked if an exception is raised. 8 | 9 | The database connection is activated and configurated in the ``[database]`` section 10 | of the application `configuration file `__. 11 | One of the parameters in this section is the location of the 12 | `__metadata__ `_ object of 13 | SQLAlchemy: 14 | 15 | - which must be explicitly created: 16 | 17 | .. code-block:: python 18 | 19 | from elixir import * 20 | from sqlalchemy import MetaData 21 | 22 | __metadata__ = MetaData() 23 | 24 | # Database definitions 25 | ... 26 | 27 | - which Nagare automatically binds to the database engine when the application 28 | is launched 29 | 30 | - where your code must explicitly register the 31 | `table definitions `_ 32 | if you are using SQLAlchemy only 33 | 34 | - where Elixir automatically register the defined entities 35 | 36 | -------------------------------------------------------------------------------- /doc/demo_installation.txt: -------------------------------------------------------------------------------- 1 | Demo installation 2 | ================= 3 | 4 | 1. Installation 5 | --------------- 6 | 7 | To install the demonstration, enter the command: 8 | 9 | .. code-block:: sh 10 | 11 | /bin/easy_install nagare.examples 12 | 13 | or, in Windows:: 14 | 15 | \Scripts\easy_install.exe nagare.examples 16 | 17 | 2. Developers installation 18 | -------------------------- 19 | 20 | If you want to work with the latest demo sources, first grab the sources from 21 | the repository: 22 | 23 | .. code-block:: sh 24 | 25 | cd 26 | git clone https://github.com/nagareproject/examples.git 27 | 28 | Second, install the demo from the sources: 29 | 30 | .. code-block:: sh 31 | 32 | cd examples 33 | /bin/python setup.py develop 34 | 35 | You can now work with the sources of the demo in ``/examples/nagare`` 36 | and, when you want to update the sources from the repository, do: 37 | 38 | .. code-block:: sh 39 | 40 | cd /examples 41 | git pull 42 | /bin/python setup.py develop 43 | 44 | 3. Lauching the demo applications 45 | --------------------------------- 46 | 47 | .. note:: 48 | 49 | On windows, replace the command ``/bin/nagare-admin`` by 50 | ``\Scripts\nagare-admin.exe`` 51 | 52 | To check the installation, the command: 53 | 54 | .. code-block:: sh 55 | 56 | /bin/nagare-admin serve 57 | 58 | must display the applications installed with the demo: 59 | 60 | .. code-block:: sh 61 | 62 | Available applications: 63 | - admin 64 | - demo 65 | - gallery 66 | - portal 67 | - wiki 68 | 69 | To launch the "demo" application, enter: 70 | 71 | .. code-block:: sh 72 | 73 | /bin/nagare-admin serve demo 74 | 75 | and browse to http://127.0.0.1:8080/demo. 76 | 77 | The "wiki", "gallery" and "portal" applications need a database so the extra step 78 | of the database creation is required before to launch them. 79 | 80 | To launch the "wiki" application, enter the commands: 81 | 82 | .. code-block:: sh 83 | 84 | /bin/nagare-admin create-db wiki 85 | /bin/nagare-admin serve wiki 86 | 87 | and browse to http://127.0.0.1:8080/wiki. 88 | 89 | for the "gallery" application, enter the commands: 90 | 91 | .. code-block:: sh 92 | 93 | /bin/nagare-admin create-db gallery 94 | /bin/nagare-admin serve gallery 95 | 96 | and browse to http://127.0.0.1:8080/gallery. 97 | 98 | or, for the "portal" application: 99 | 100 | .. code-block:: sh 101 | 102 | /bin/nagare-admin create-db portal 103 | /bin/nagare-admin serve portal 104 | 105 | and browse to http://127.0.0.1:8080/portal. 106 | 107 | -------------------------------------------------------------------------------- /doc/demo_upgrade.txt: -------------------------------------------------------------------------------- 1 | Demo upgrade 2 | ============ 3 | 4 | 1. Virtual environment installation 5 | ----------------------------------- 6 | 7 | If the demo installation you want to upgrade is located into a virtual 8 | environment, execute the followings commands: 9 | 10 | on Linux: 11 | 12 | .. code-block:: sh 13 | 14 | /bin/easy_install -U nagare.examples 15 | 16 | on Windows:: 17 | 18 | \Scripts\easy_install.exe -U nagare.examples 19 | 20 | 2. System-wide installation 21 | --------------------------- 22 | 23 | If the demo installation was installed system-wide, execute: 24 | 25 | on Linux: 26 | 27 | .. code-block:: sh 28 | 29 | /bin/easy_install -U 'nagare.examples' 30 | 31 | on Windows:: 32 | 33 | \Scripts\easy_install.exe -U nagare.examples 34 | 35 | 3. Developer installation 36 | ------------------------- 37 | 38 | If your demo installation was directly a checkout from the Github repository, 39 | execute: 40 | 41 | .. code-block:: sh 42 | 43 | cd examples 44 | git pull 45 | /bin/python setup.py develop 46 | 47 | -------------------------------------------------------------------------------- /doc/description.txt: -------------------------------------------------------------------------------- 1 | *And Now For Something Completely Different* 2 | ============================================ 3 | 4 | .. container:: description-left 5 | 6 | .. image:: img/banner.png 7 | 8 | .. container:: description-right 9 | 10 | .. include:: ../README.rst 11 | :start-line: 3 12 | 13 | 14 | .. raw:: html 15 | 16 |

Take an open-minded tour to Nagare

17 | 18 | Try it ! Play with it ! Adopt it ! 19 | 20 | And never look back. 21 | 22 | .. raw:: html 23 | 24 |
25 | 26 | **Nagare** is the japanese word for "flow" or "smooth transition". 27 | 28 | A reference to the control flow of an application, the name of the highest 29 | creativity state of mind, and, in the "Ki no nagare" principle of martial arts, 30 | the perfect association of energy and agility. 31 | 32 | -------------------------------------------------------------------------------- /doc/entry_points.txt: -------------------------------------------------------------------------------- 1 | Entry points 2 | ============ 3 | 4 | The entry points are a mecanism to extend the framework. 5 | 6 | How the entry points are declared in the ``setup.py`` file of your project 7 | is described in the `Setuptools documentation `_. 8 | 9 | The framework knows about the following entry points: 10 | 11 | - ``nagare.applications``: this entry point is the most often used as it registers 12 | an application to the framework. 13 | 14 | An application can be an instance of ``WSGIApp``, configurated with a 15 | component factory, or a direct component factory which, in this case, will 16 | be automatically wrapped into a default ``WSGIApp`` object. 17 | 18 | - ``nagare.publishers``: these entry points defined the publishers that can be 19 | selected with the ``type`` parameter of the 20 | `publisher configuration file `__ 21 | 22 | The framework publishers are defined into the :mod:`nagare.publishers` 23 | package. The class :class:`nagare.publishers.common.Publisher` is the base class of 24 | all the publishers. 25 | 26 | - ``nagare.sessions``: there entry points defined the sessions manager that can be 27 | selected with the ``type`` paramater of the 28 | `publisher configuration file `__. 29 | 30 | The framework sessions managers are defined into the :mod:`nagare.sessions` 31 | package. The class :class:`nagare.sessions.common.Sessions` 32 | is the base class of all the sessions managers. 33 | 34 | - ``nagare.commands``: these entry points defined new administrative commands 35 | (launched with :doc:`nagare-admin`) 36 | 37 | All the build-in administrative commands of the framework are defined into 38 | the :mod:`nagare.admin` package. The class :class:`nagare.admin.command.Command` 39 | is the interface an administrative command must respect. 40 | 41 | - ``nagare.admin``: these entry points defined components that will be rendered 42 | into the "Nagare Administration interface" page (launched with ``nagare-admin serve admin``). 43 | See the ``admin/interface/info.py`` and ``admin/interface/applications.py`` files. 44 | 45 | -------------------------------------------------------------------------------- /doc/features.txt: -------------------------------------------------------------------------------- 1 | Features 2 | ======== 3 | 4 | Nagare is a components based framework: a Nagare application is a composition 5 | of interacting components each one with its own state and workflow kept on the 6 | server. Each component can have one or several views that are composed to 7 | generate the final web page. This enables the developers to reuse or write 8 | highly reusable components easily and quickly. 9 | 10 | Thanks to Stackless Python, Nagare is also a continuation-based web framework 11 | which enables to code a web application like a desktop application, with no 12 | need to split its control flow in a multitude of controllers and with the 13 | automatic handling of the back, fork and refresh actions from the browser. 14 | 15 | Its component model and use of the continuation come from the famous 16 | `Seaside `_ Smalltalk framework. 17 | 18 | Python is a great language and Nagare makes full use of it: 19 | 20 | - the views of the components are Python methods rendering a DOM tree, 21 | built in programmatic XHTML or from a template, 22 | - the control flow of the components is coded in Python, thanks to the 23 | continuation, 24 | - data management in DBMS using SQLAlchemy or its declarative layer Elixir, 25 | - if a Python function is given instead of an expected Javascript code, Nagare 26 | automatically generates a translation from Python to Javascript. 27 | 28 | Furthermore Nagare integrates the best tools and standard from the Python world. 29 | For example: 30 | 31 | - WSGI: binds the application to several possible publishers, 32 | - lxml: generates the DOM trees and brings to Nagare the full set of 33 | XML features (XSL, XPath, Schemas ...), 34 | - setuptools: installs, deploys and extends the Nagare framework and the 35 | Nagare applications too, 36 | - PEAK Rules: generic methods are heavily used in Nagare, to associate views 37 | to components, to define security rules, to translate Python 38 | code to Javascript ... 39 | - WebOb: for its Request and Response Objects. 40 | 41 | Using Nagare frees the developer from the classic burden of web programming: 42 | 43 | - No more manual URL mapping. Nagare directly associates a Python callback 44 | to a link or a field of a form. 45 | - No global session object. All the states of all components are kept on 46 | the server. 47 | - Easy development with Nagare own Python multi-threaded server including 48 | automatic reloading and possible clustering deployment using a memcached 49 | server. 50 | - Automatic conversion of the parameters received to Unicode and automatic 51 | generation of UTF-8 encoded web pages. Nagare is a full Unicode framework. 52 | - Possible automatic generation of, always 100% valid, XHTML or HTML according 53 | to the browser capability. 54 | - Transparent Ajax rendering of a component, without any Python or Javascript 55 | code to add. 56 | 57 | -------------------------------------------------------------------------------- /doc/framework_installation.txt: -------------------------------------------------------------------------------- 1 | Nagare installation 2 | =================== 3 | 4 | 1. Stackless Python installation 5 | -------------------------------- 6 | 7 | The Nagare framework uses Stackless Python (version 2.5.2 or above). 8 | 9 | 1.1. Linux installation 10 | ~~~~~~~~~~~~~~~~~~~~~~~ 11 | 12 | First, search into your Linux distribution packages if Stackless Python >= 2.5.2 13 | is available. 14 | 15 | Else, to install Stackless Python from its sources, to the ** 16 | directory, do: 17 | 18 | .. code-block:: sh 19 | 20 | cd /tmp 21 | wget http://www.stackless.com/binaries/stackless-2714-export.tar.xz 22 | tar xvf stackless-2714-export.tar.xz 23 | 24 | cd stackless-2714-export 25 | ./configure --prefix= 26 | make all 27 | make install 28 | 29 | .. note:: 30 | 31 | - If the is a system directory (like ``/opt`` or ``/usr/local``), 32 | you will have to be logged as **root** to launch the last ``make install`` command. 33 | 34 | - Some packages can be needed for the installation of Stackless Python and 35 | Nagare, especially some "devel" ones. For example, with debian/ubuntu, you 36 | would have to install: 37 | 38 | .. code-block:: sh 39 | 40 | sudo apt-get install ca-certificates wget gcc libbz2-dev libgdbm-dev libc6-dev libreadline6-dev libsqlite3-dev libssl-dev make xz-utils zlib1g-dev libyaml-dev libxml2-dev libxslt1-dev 41 | 42 | 1.2. Windows installation 43 | ~~~~~~~~~~~~~~~~~~~~~~~~~ 44 | 45 | To install Stackless Python on Windows, download the `dedicated installer 46 | `_ and launch it. 47 | 48 | 2. Nagare installation 49 | ---------------------- 50 | 51 | The framework can be installed system-wide where every users can use it or 52 | into a dedicated directory called a "virtual environment". 53 | 54 | Creating a virtual environment is the standard and recommended way because such 55 | installation doesn't modify the configuration of the stackless Python interpreter 56 | and doesn't require root privileges. 57 | 58 | 2.1. Virtual environment installation 59 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 60 | 61 | To create a virtual environment into the ** directory, download 62 | `virtualenv `_ and launch it **with Stackless Python**. 63 | 64 | On Linux: 65 | 66 | .. code-block:: sh 67 | 68 | cd /tmp 69 | wget https://pypi.python.org/packages/d4/0c/9840c08189e030873387a73b90ada981885010dd9aea134d6de30cd24cb8/virtualenv-15.1.0.tar.gz 70 | tar xvf virtualenv-15.1.0.tar.gz 71 | 72 | /bin/python virtualenv-15.1.0/virtualenv.py 73 | 74 | On Windows: 75 | 76 | 1. Download and launch the `Python Win32 extensions `_ 77 | 78 | 2. Download the 79 | `virtualenv archive `_ 80 | and extract the file ``virtualenv.py`` (you may need an archiver like ``7-zip`` or ``winrar``). 81 | 82 | then launch:: 83 | 84 | \python.exe virtualenv.py 85 | 86 | The final step is to install the framework into this newly created virtual 87 | environment. 88 | 89 | On Linux: 90 | 91 | .. code-block:: sh 92 | 93 | /bin/easy_install 'nagare[full]' 94 | 95 | On Windows:: 96 | 97 | \Scripts\easy_install.exe nagare[full] 98 | 99 | 2.2. System-wide installation 100 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 101 | 102 | If Stackless Python is installed in a system directory, you will need to have 103 | the **root** privileges. 104 | 105 | First, install ``easy_install`` by downloading 106 | `ez_setup.py `_ and launch it 107 | **with stackless Python**. 108 | 109 | Second, install the framework on Linux: 110 | 111 | .. code-block:: sh 112 | 113 | /bin/easy_install 'nagare[full]' 114 | 115 | or, on Windows:: 116 | 117 | \Scripts\easy_install.exe nagare[full] 118 | 119 | See the following chapter if you don't want to install the framework with all 120 | its modules. 121 | 122 | 3. Installation options 123 | ----------------------- 124 | 125 | The framework installation is modular. The command ``easy_install nagare`` installs 126 | only the framework core with the minimum number of features. More features, 127 | called "extras", can be installed with the syntax ``easy_install 'nagare[extra1,extra2...]'``. 128 | 129 | Currently, the available extras are: 130 | 131 | - ``debug`` -- install the debug web page, displayed when an exception is raised 132 | - ``database`` -- install the Python modules needed, for an application, to access relational databases 133 | - ``doc`` -- install the Python modules needed to generate the framework documentation 134 | - ``test`` -- install the unit tests manager 135 | - ``i18n`` -- install the internationalization modules 136 | 137 | - ``full`` -- install all of the above extras (i.e ``easy_install 'nagare[full]'`` 138 | is equivalent to ``easy_install 'nagare[debug,database,doc,test,i18n]'``) 139 | 140 | 4. Developers framework installation 141 | ------------------------------------ 142 | 143 | To work with the latest framework sources directly from its repository, first 144 | create a virtual environment into the ** directory: download ``virtualenv`` 145 | and launch it **with Stackless Python**. 146 | 147 | .. code-block:: sh 148 | 149 | cd /tmp 150 | wget https://pypi.python.org/packages/d4/0c/9840c08189e030873387a73b90ada981885010dd9aea134d6de30cd24cb8/virtualenv-15.1.0.tar.gz 151 | tar xvf virtualenv-15.1.0.tar.gz 152 | 153 | /bin/python virtualenv-15.1.0/virtualenv.py 154 | 155 | Second, grab the framework sources from the repository: 156 | 157 | .. code-block:: sh 158 | 159 | cd 160 | git clone https://github.com/nagareproject/core.git 161 | 162 | Third, install the framework from the sources: 163 | 164 | .. code-block:: sh 165 | 166 | cd core 167 | /bin/python setup.py develop 168 | /bin/easy_install 'nagare[full]' 169 | 170 | You can now work with the sources of the framework in ``/core/nagare`` 171 | and, when you want to update the sources from the repository, do: 172 | 173 | .. code-block:: sh 174 | 175 | cd /core 176 | git pull 177 | /bin/python setup.py develop 178 | 179 | 5. Testing the installation 180 | --------------------------- 181 | 182 | To test the installation, launch the adminstrative interface: 183 | 184 | .. code-block:: sh 185 | 186 | /bin/nagare-admin serve admin 187 | 188 | or, in Windows:: 189 | 190 | \Scripts\nagare-admin.exe serve admin 191 | 192 | which must results in the following display: 193 | 194 | .. code-block:: sh 195 | 196 | Application 'app admin' registered as '/admin' 197 | Application 'app admin' registered as '/' 198 | serving on http://127.0.0.1:8080 199 | 200 | Then you can browse at http://127.0.0.1:8080/admin to look at the administrative 201 | interface. 202 | 203 | -------------------------------------------------------------------------------- /doc/framework_upgrade.txt: -------------------------------------------------------------------------------- 1 | Nagare upgrade 2 | ============== 3 | 4 | 1. Virtual environment installation 5 | ----------------------------------- 6 | 7 | If the Nagare installation you want to upgrade is located into a virtual 8 | environment, execute the followings commands: 9 | 10 | on Linux: 11 | 12 | .. code-block:: sh 13 | 14 | /bin/easy_install -U 'nagare[full]' 15 | 16 | on Windows:: 17 | 18 | \Scripts\easy_install.exe -U nagare[full] 19 | 20 | 2. System-wide installation 21 | --------------------------- 22 | 23 | If the Nagare installation was installed system-wide, execute: 24 | 25 | on Linux: 26 | 27 | .. code-block:: sh 28 | 29 | /bin/easy_install -U 'nagare[full]' 30 | 31 | on Windows:: 32 | 33 | \Scripts\easy_install.exe -U nagare[full] 34 | 35 | 3. Developer installation 36 | ------------------------- 37 | 38 | If your Nagare installation was directly a checkout from the Github repository, 39 | execute: 40 | 41 | .. code-block:: sh 42 | 43 | cd core 44 | git pull 45 | /bin/easy_install setup.py develop 46 | 47 | -------------------------------------------------------------------------------- /doc/img/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nagareproject/core/2791fe7b00ecb08e73362fa406dfd7807e00c209/doc/img/banner.png -------------------------------------------------------------------------------- /doc/img/tutorial1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nagareproject/core/2791fe7b00ecb08e73362fa406dfd7807e00c209/doc/img/tutorial1.png -------------------------------------------------------------------------------- /doc/index.txt: -------------------------------------------------------------------------------- 1 | .. Nagare documentation master file, created by 2 | sphinx-quickstart on Fri Sep 29 15:07:51 2017. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | .. mermaid:: 7 | 8 | graph TD 9 | rabbitmq --> channel[rabbitmq channel] 10 | channel -->|rabbitmq publisher| publisher(publisher) 11 | publisher --> transaction(transaction) 12 | transaction -->|return or raise| base_exceptions(base_exceptions) 13 | base_exceptions -->|overwrite| http_exceptions(http_exceptions) 14 | http_exceptions -->|overwrite| exceptions(exceptions) 15 | exceptions --> |404 page|statics(statics) 16 | exceptions --> |raise| application{application} 17 | i18n -->|locale creation| i18nlocale(i18nlocale) 18 | statics -->|early| local(local) 19 | local --> i18n(i18n) 20 | i18nlocale --> sessions 21 | publisher -->|check concurrence| sessions 22 | sessions --> callbacks 23 | local -->|lock creation| sessions 24 | sessions -->|get state| state(state) 25 | state --> callbacks(callbacks) 26 | callbacks --> redirect_after_post(redirect_after_post) 27 | redirect_after_post --> presentation(presentation) 28 | presentation --> top(top) 29 | top --> serializer(serializer) 30 | serializer --> application(application) 31 | subgraph services 32 | database 33 | logging 34 | end 35 | 36 | Nagare documentation 37 | ==================== 38 | 39 | .. toctree:: 40 | :maxdepth: 1 41 | :caption: About 42 | 43 | Description 44 | features 45 | License 46 | Changelog 47 | 48 | .. toctree:: 49 | :maxdepth: 1 50 | :caption: Using it 51 | 52 | quickstart 53 | Installation 54 | Upgrade 55 | nagare-admin 56 | Application configuration 57 | Publisher configuration 58 | Application deployment 59 | Third party applications 60 | 61 | .. toctree:: 62 | :maxdepth: 1 63 | :caption: Tutorial 64 | 65 | Part 1 66 | Part 2 67 | Part 3 68 | Part 4 69 | Wiki 70 | 71 | .. toctree:: 72 | :maxdepth: 1 73 | :caption: Developer documentation 74 | 75 | Application creation 76 | Presentation service 77 | Components model 78 | Databases 79 | Callbacks and forms 80 | Renderers 81 | Significative URLs 82 | WSGI Application 83 | Internationalization and localization 84 | Log service 85 | Object references 86 | Entry points 87 | 88 | .. toctree:: 89 | :maxdepth: 1 90 | :caption: Examples 91 | 92 | Demo installation 93 | Demo upgrade 94 | 95 | Indices and tables 96 | ================== 97 | 98 | * :ref:`genindex` 99 | * :ref:`modindex` 100 | -------------------------------------------------------------------------------- /doc/license.txt: -------------------------------------------------------------------------------- 1 | Nagare license 2 | ============== 3 | 4 | .. include:: ../LICENSE.txt 5 | 6 | -------------------------------------------------------------------------------- /doc/log.txt: -------------------------------------------------------------------------------- 1 | The log service 2 | =============== 3 | 4 | It's often useful to log some information during the execution of a Web 5 | application to trace the execution of the requests and to provide useful 6 | information for debugging when something goes wrong. The most common use-case is 7 | to log the exceptions tracebacks to a file on the server that provide the context 8 | where the error occurred so that we can fix the problem without disclosing 9 | sensitive information to the end-users. 10 | 11 | Python ships with a `logging`_ module that provides everything we need in a Web 12 | development context. However, it is relatively complex to configure and it's 13 | configuration is a singleton, which is problematic in a multi-applications setup 14 | (that is, when the same ``nagare-admin serve`` process serves more than one 15 | application). 16 | 17 | In order to solve these problems, Nagare provides its own :mod:`nagare.log` service 18 | , which is built on top of the `logging`_ module but greatly 19 | simplifies its usage and configuration for common use-cases. 20 | 21 | 22 | Basic Usages 23 | ------------ 24 | 25 | Using The default logger 26 | ~~~~~~~~~~~~~~~~~~~~~~~~ 27 | 28 | By default, when you serve Web applications with the ``nagare-admin serve`` 29 | utility, Nagare creates a dedicated logger, handler and formatter for each 30 | application, and activates them: this is the purpose of the 31 | :class:`nagare.log.configure` and :class:`nagare.log.activate` functions. 32 | 33 | You can use the module level functions of the ``log`` module to write messages 34 | to the default logger of the *currently running* application, as shown in the 35 | example below: 36 | 37 | .. code-block:: python 38 | :emphasize-lines: 8 39 | 40 | from nagare import presentation, log 41 | 42 | class Root(object): 43 | pass 44 | 45 | @presentation.render_for(Root) 46 | def render(self, h, *args): 47 | log.info('Rendering the Root component') 48 | # some rendering code... 49 | return h.root 50 | 51 | app = Root 52 | 53 | 54 | Then, each time the default view of the ``Root`` component is rendered, this 55 | line should appear in the console shell (which is the output of the default 56 | logger): 57 | 58 | .. code-block:: text 59 | 60 | 2012-06-14 10:22:38,379 - nagare.application.myapp - INFO - Rendering the root component 61 | 62 | As you see, the messages are issued in the ``nagare.application.myapp`` 63 | namespace, which is the namespace of the messages coming from the Nagare 64 | application called ``myapp``. 65 | 66 | Here is the full listing of the module-level functions of the ``nagare.log`` 67 | module: 68 | 69 | ====================================== ================================================================= 70 | Function Effect 71 | ====================================== ================================================================= 72 | ``log.debug(msg, *args, **kw)`` Logs a message with ``DEBUG`` level on the application logger 73 | ``log.info(msg, *args, **kw)`` Logs a message with ``INFO`` level on the application logger 74 | ``log.warning(msg, *args, **kw)`` Logs a message with ``WARNING`` level on the application logger 75 | ``log.error(msg, *args, **kw)`` Logs a message with ``ERROR`` level on the application logger 76 | ``log.critical(msg, *args, **kw)`` Logs a message with ``CRITICAL`` level on the application logger 77 | ``log.exception(msg, *args, **kw)`` Logs a message with ``ERROR`` level on the application logger, 78 | and also logs the current exception information 79 | ``log.log(level, msg, *args, **kw)`` Logs a message with the specified level on the application 80 | logger 81 | ====================================== ================================================================= 82 | 83 | All these functions accept variable arguments and keyword arguments, which are 84 | documented in the ``logging`` module, `here `_. 85 | 86 | Overriding the default configuration 87 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 88 | 89 | The ``[[logger]]``, ``[[hander]]`` and ``[[formatter]]`` sub-sections of the 90 | ``[logging]`` section in the application configuration file are dedicated to 91 | configure the default application logger. 92 | 93 | The default logging setup is equivalent to the following ``[logging]`` section, 94 | where ```` is replaced by the name of the application (please 95 | refer to the ``logging``'s module `configuration file format`_ for a better 96 | understanding of the configuration of loggers, handlers and formatters): 97 | 98 | .. code-block:: ini 99 | 100 | [logging] 101 | 102 | [[logger]] 103 | qualname=nagare.application. 104 | level=INFO 105 | propagate=1 106 | 107 | [[handler]] 108 | class=StreamHandler 109 | args=(sys.stderr,) 110 | 111 | [[formatter]] 112 | format=%(asctime)s - %(name)s - %(levelname)s - %(message)s 113 | 114 | As you can see, by default, the log messages with a level greater or equal to 115 | ``INFO`` are printed to ``sys.stderr`` via a ``StreamHandler``. 116 | 117 | It's possible to override this default ``logging`` configuration by adding your 118 | own ``[logging]`` section to the application configuration file. Here, the 119 | logging level of the application is set to ``DEBUG``: 120 | 121 | .. code-block:: ini 122 | 123 | [logging] 124 | [[logger]] 125 | level=DEBUG 126 | 127 | Here, a new handler is defined to log the messages to the file ``/tmp/myapp.log``. 128 | We use a ``RotatingFileHandler`` instead of the classical ``FileHandler`` since 129 | we want the file to be log-rotated in order to prevent it from becoming too large: 130 | 131 | .. code-block:: ini 132 | 133 | [logging] 134 | [[handler]] 135 | class=handlers.RotatingFileHandler 136 | args="('/tmp/myapp.log', 'a', 10485760, 10, 'UTF-8')" 137 | 138 | Using the Nagare IDE 139 | ~~~~~~~~~~~~~~~~~~~~ 140 | 141 | When the Nagare IDE is launched at the same time than your application 142 | (i.e ``nagare-admin serve ide``), a second handler is bound to the default 143 | logger so that all the messages are also sent to the 144 | `IDE log panel `_. 145 | 146 | Advanced Usages 147 | --------------- 148 | 149 | Creating other application loggers 150 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 151 | 152 | As seen above, Nagare makes it easy to log messages in an application. However, 153 | as your application becomes more complex and is broken down in several modules 154 | and packages, it may become hard to track down where you sent a particular message 155 | or to analyze the log file which is now filled with numerous log messages. 156 | 157 | In this situation, it's generally useful to organize the log messages into 158 | namespaces, so that we can enable/disable the logging of messages in some 159 | namespaces or send messages with different namespaces to different handlers. 160 | 161 | For this purpose, Nagare offers a ``log.get_logger(namespace)`` function that 162 | creates a logger which puts the messages into the specified namespace. To create a 163 | new logger for your application, use a relative name starting with a dot. In this 164 | case, the new namespace is relative to the application namespace, e.g. 165 | ``nagare.application.myapp``. 166 | 167 | The logger object offers the same functions as the ``log`` module for writing 168 | messages, that is ``logger.debug``, ``logger.info``, ``logger.warning``, 169 | ``logger.error``, ``logger.critical``, ``logger.exception`` and ``logger.log``, 170 | with the same parameters. 171 | 172 | In this example, we will log all the messages generated in the views to a 173 | specific ``nagare.application.myapp.ui`` namespace: 174 | 175 | .. code-block:: python 176 | 177 | from nagare import log 178 | 179 | @presentation.render_for(Root) 180 | def render(self, h, *args): 181 | log.get_logger('.ui').debug('Rendering the Root component') 182 | # some rendering code... 183 | return h.root 184 | 185 | and you can see, the message is now attached to the ``nagare.application.myapp.myapp.ui`` 186 | namespace: 187 | 188 | .. code-block:: text 189 | 190 | 2012-06-14 10:22:38,379 - nagare.application.myapp.ui - INFO - Rendering the root component 191 | 192 | Being under the ``nagare.application.myapp`` namespace, this logger propagates 193 | the messages to the default logger. Also this logger inherits the default logger 194 | configuration, which can be overridden: 195 | 196 | .. code-block:: ini 197 | 198 | [logging] 199 | [[loggers]] 200 | keys=ui # Declare the new logger 201 | 202 | [[logger_ui]] 203 | qualname=.ui # Relative namespace 204 | level=CRITICAL 205 | # No specific handler: propagate the messages to the default application logger 206 | handlers= 207 | 208 | Here the log level of the logger is set to ``CRITICAL``. 209 | 210 | Or a more complex configuration can be created, for example to log the messages 211 | to a specific log file: 212 | 213 | .. code-block:: ini 214 | 215 | [logging] 216 | [[loggers]] 217 | keys=ui # Declare the new logger 218 | 219 | [[handlers]] 220 | keys=logfile # Declare the new handler 221 | 222 | [[formatters]] 223 | keys=simple # Declare the new formatter 224 | 225 | [[logger_ui]] 226 | qualname=.ui # Relative namespace 227 | handlers=logfile 228 | propagate=0 # Don't propagate the messages to the default application logger 229 | 230 | [[handler_logfile]] 231 | class=handlers.RotatingFileHandler 232 | args="('/tmp/myapp_ui.log', 'a', 10485760, 10, 'UTF-8')" # Specific log file 233 | formatter=simple 234 | 235 | [[formatter_simple]] 236 | format=%(levelname)s: %(message)s 237 | 238 | Creating other loggers 239 | ~~~~~~~~~~~~~~~~~~~~~~ 240 | 241 | Any other general loggers can be created the same way, by just giving a full 242 | absolute namespace. 243 | 244 | For example the `SQLAlchemy documentation `_ 245 | states the name of the SQL query logger is ``sqlalchemy.engine``. So this 246 | logging configuration will display all the generated SQL queries: 247 | 248 | .. code-block:: ini 249 | 250 | [logging] 251 | [[loggers]] 252 | keys=sa 253 | 254 | [[handlers]] 255 | keys=stderr 256 | 257 | [[formatters]] 258 | keys=multilines 259 | 260 | [[logger_sa]] 261 | qualname=sqlalchemy.engine # The absolute SQLAlchemy logger namespace 262 | level=INFO 263 | handlers=stderr 264 | 265 | [[handler_stderr]] 266 | class=StreamHandler 267 | args=(sys.stderr,) 268 | formatter=multilines 269 | 270 | [[formatter_multilines]] 271 | format={ %(message)s } 272 | 273 | 274 | .. _`logging`: https://docs.python.org/2/library/logging.html 275 | .. _`Configuration file format`: https://docs.python.org/2/library/logging.config.html#configuration-file-format 276 | 277 | -------------------------------------------------------------------------------- /doc/mailing_lists.txt: -------------------------------------------------------------------------------- 1 | Mailing List 2 | ============ 3 | 4 | For help and announcements, you can subscribe to the 5 | `users group `_. 6 | 7 | -------------------------------------------------------------------------------- /doc/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=python -msphinx 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | set SPHINXPROJ=Nagare 13 | 14 | if "%1" == "" goto help 15 | 16 | %SPHINXBUILD% >NUL 2>NUL 17 | if errorlevel 9009 ( 18 | echo. 19 | echo.The Sphinx module was not found. Make sure you have Sphinx installed, 20 | echo.then set the SPHINXBUILD environment variable to point to the full 21 | echo.path of the 'sphinx-build' executable. Alternatively you may add the 22 | echo.Sphinx directory to PATH. 23 | echo. 24 | echo.If you don't have Sphinx installed, grab it from 25 | echo.http://sphinx-doc.org/ 26 | exit /b 1 27 | ) 28 | 29 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 30 | goto end 31 | 32 | :help 33 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 34 | 35 | :end 36 | popd 37 | -------------------------------------------------------------------------------- /doc/nagare-admin.txt: -------------------------------------------------------------------------------- 1 | nagare-admin 2 | ============ 3 | 4 | The ``nagare-admin`` command is used to create, configure and launch applications. 5 | 6 | Usage 7 | ----- 8 | 9 | You can get a comprehensive list of the available commands by invoking ``nagare-admin`` 10 | with the ``--help`` or ``-h`` options: 11 | 12 | .. code-block:: sh 13 | 14 | /bin/nagare-admin --help 15 | 16 | :: 17 | 18 | Usage : nagare-admin 19 | 20 | with : 21 | - batch : Execute Python statements from a file 22 | - create-app : Create an application skeleton 23 | - create-db : Create the database of an application 24 | - create-rules: Create the rewrite rules 25 | - drop-db : Drop the database of an application 26 | - info : Display various informations 27 | - serve : Launch an application 28 | - serve-module: Launch a python module 29 | - shell : Launch a shell 30 | 31 | And you can get an help about a specific command by entering : 32 | 33 | .. code-block:: sh 34 | 35 | /bin/nagare-admin --help 36 | 37 | Full commands reference 38 | ----------------------- 39 | 40 | batch 41 | ~~~~~ 42 | 43 | The ``batch`` command executes a python file. 44 | 45 | .. code-block:: sh 46 | 47 | /bin/nagare-admin batch [batch options ...] 48 | 49 | Once launched, two variables are available: 50 | 51 | - ``apps`` is a dictionary that associates the name of an application to its 52 | activated application object. 53 | - ``session`` is the SQLAlchemy session (don't forget the ``session.commit()`` 54 | statement at the end of your batch if database entities are manipulated) 55 | 56 | The available options are: 57 | 58 | -d, --debug display the generated SQL requests 59 | 60 | create-app 61 | ~~~~~~~~~~ 62 | 63 | The ``create-app`` command creates the whole directories and files structure 64 | of a skeleton application: 65 | 66 | .. code-block:: sh 67 | 68 | /bin/nagare-admin create-app 69 | 70 | See :doc:`application_creation` 71 | 72 | create-db 73 | ~~~~~~~~~ 74 | 75 | The ``create-db`` command creates the database tables from an application 76 | entities description: 77 | 78 | .. code-block:: sh 79 | 80 | /bin/nagare-admin create-db 81 | 82 | The ``metadata`` parameter of the ``[database]`` section of 83 | :doc:`configuration_file` must be an :doc:`object_references` to an 84 | `SQLAlchemy metadata object `_. 85 | The usage of the metadata object is described in :doc:`database`. 86 | 87 | The available options are: 88 | 89 | --no-populate by default, after the tables creation, the function 90 | referenced by the ``populate`` parameter of the ``[database]`` 91 | section is called to initialize the database. The 92 | ``--no-populate`` option disable this behaviour. 93 | --drop if this option is given, the database tables are dropped 94 | before to be re-created. 95 | -d, --debug display the generated SQL requests 96 | 97 | create-rules 98 | ~~~~~~~~~~~~ 99 | 100 | The ``create-rules`` command generates rewrite rules for the ``apache``, 101 | ``lighttpd`` or ``nginx`` web servers. These rules associate the URL 102 | ``/static/`` to the directory path of the static contents of the 103 | application. With such rules activated, the web server will deliver the static 104 | contents instead of Nagare. 105 | 106 | .. code-block:: sh 107 | 108 | /bin/nagare-admin create-rules [ ...] 109 | 110 | If no applications are given, the rules for all the registered applications are 111 | created. When a list of applications is given, only the rules for these 112 | applications are generated. 113 | 114 | The available options are: 115 | 116 | -a, --apache create rules for Apache (default) 117 | -l, --lighttpd create rules for Lighttpd 118 | -n, --nginx create rules for Nginx 119 | 120 | drop-db 121 | ~~~~~~~ 122 | 123 | The ``drop-db`` command deletes the database tables of the application: 124 | 125 | .. code-block:: sh 126 | 127 | /bin/nagare-admin drop-db 128 | 129 | The ``metadata`` parameter of the ``[database]`` section of 130 | :doc:`configuration_file` must be an :doc:`object_references` to an 131 | `SQLAlchemy metadata object `_. 132 | The usage of the metadata object is described in :doc:`database`. 133 | 134 | The available options are: 135 | 136 | -d, --debug display the generated SQL requests 137 | 138 | info 139 | ~~~~ 140 | 141 | The ``info`` command displays informations about your system environment 142 | (suitable to be added into a bug ticket): 143 | 144 | .. code-block:: sh 145 | 146 | /bin/nagare-admin info 147 | 148 | serve 149 | ~~~~~ 150 | 151 | The ``serve`` command launches one or several applications: 152 | 153 | .. code-block:: sh 154 | 155 | /bin/nagare-admin serve ... 156 | 157 | The ** argument can be the name of a registered application or 158 | an :doc:`application configuration file `. 159 | 160 | Without any parameters, the command displays the list of all the registered 161 | applications. 162 | 163 | The available options are: 164 | 165 | --host by default, the publisher only accepts requests on the 166 | local interface. If you want to accept external requests, 167 | set this parameter to the internet address of your external 168 | interface or to '0.0.0.0' to listen to on all the interfaces 169 | of your system. This option overwrites the ``host`` parameter 170 | of the :doc:`publisher_file` 171 | -p, --port port where to listen to the requests. This option overwrites 172 | the ``port`` parameter of the :doc:`publisher_file` 173 | -c, --conf path to the :doc:`publisher_file`. If not given, the 174 | standalone Python threaded HTTP server is used. 175 | -d, --debug display the web debug page when an exception occurs. This 176 | option overwrites the ``debug`` option of 177 | :doc:`configuration_file`. 178 | The ``nagare[debug]`` extra must be installed. 179 | --reload This option activates the reloader process which automatically 180 | detect source changes and re-launch the application. 181 | 182 | serve-module 183 | ~~~~~~~~~~~~ 184 | 185 | The ``serve-module`` command directly launches a component. It's a simplified 186 | version of the ``server`` command, that don't need a configuration file, mainly 187 | used to quickly prototype some code that doesn't need database accesses: 188 | 189 | .. code-block:: sh 190 | 191 | /bin/nagare-admin serve-module 192 | 193 | */url* will be the URL of the application. 194 | 195 | The *path_to_module* parameter can be a filesystem path as: 196 | 197 | .. code-block:: sh 198 | 199 | /bin/nagare-admin serve-module /tmp/nagare/examples/counter.py:Counter counter 200 | 201 | or a module name as: 202 | 203 | .. code-block:: sh 204 | 205 | /bin/nagare-admin serve-module nagare.examples.counter:Counter counter 206 | 207 | The available options are: 208 | 209 | --host by default, the publisher only accepts requests on the 210 | local interface. If you want to accept external requests, 211 | set this parameter to the internet address of your external 212 | interface or to '0.0.0.0' to listen to on all the interfaces 213 | of your system. 214 | -p, --port port where to listen to the requests (default 8080). 215 | --no-debug by default, the web debug page is activated. Desactivated 216 | it with this option. 217 | 218 | .. warning:: 219 | 220 | The ``serve-module`` command always uses the standalone publisher and sessions 221 | manager, with a reloader activated. So don't use it in production. 222 | 223 | shell 224 | ~~~~~ 225 | 226 | The ``shell`` command launches an interactive Python (or IPython, if available) 227 | interpreter: 228 | 229 | .. code-block:: sh 230 | 231 | /bin/nagare-admin shell ... 232 | 233 | Once launched, two variables are available: 234 | 235 | - ``apps`` is a dictionary that associates the name of an application to its 236 | activated application object. 237 | - ``session`` is the SQLAlchemy session (don't forget the ``session.commit()`` 238 | statement if database entities are manipulated) 239 | 240 | The available options are: 241 | 242 | 243 | --plain always launch a plain Python shell, even if IPython is 244 | available 245 | -d, --debug display the generated SQL requests 246 | 247 | -------------------------------------------------------------------------------- /doc/object_references.txt: -------------------------------------------------------------------------------- 1 | Object references 2 | ================= 3 | 4 | Object references are textual references to Python objects. There are 5 | especially used in the :doc:`application files ` to indicate what 6 | Python objects to use. 7 | 8 | The general form of an object reference is: `` :``. 9 | Given such an object reference, the :class:`nagare.admin.reference.load_object()` method 10 | returns a tuple where the first item is the Python object, and the second item is the 11 | `distribution `_ 12 | where this object is located, if found. 13 | 14 | The available ``schemas`` are: 15 | 16 | - ``python :`` -- a reference to an object in a 17 | Python module. The Python module is searched using ``sys.path``. This 18 | schema is also used by default if no schema is given. 19 | 20 | .. code-block:: pycon 21 | 22 | >>> from nagare.admin import util 23 | >>> util.load_object('python xml.sax.xmlreader:XMLReader') 24 | (, None) 25 | >>> util.load_object('xml.sax.xmlreader:XMLReader') 26 | (, None) 27 | 28 | - ``file :`` -- a reference to an object in a Python 29 | file. 30 | 31 | .. code-block:: pycon 32 | 33 | >>> from nagare.admin import util 34 | >>> util.load_object('file /usr/lib/python2.5/xml/sax/xmlreader.py:XMLReader') 35 | (, None) 36 | 37 | - ``app `` -- a reference to a registered Nagare application 38 | entry point. 39 | 40 | .. code-block:: pycon 41 | 42 | >>> from nagare.admin import util 43 | >>> util.load_object('app admin') 44 | (, nagare 0.1.0 (/home/apr/projects/nagare/dev/src/nagare/core)) 45 | 46 | - ``egg :`` -- a reference to a specific 47 | registered Nagare application entry point of a distribution. 48 | 49 | .. code-block:: pycon 50 | 51 | >>> from nagare.admin import util 52 | >>> util.load_object('egg nagare.examples:wiki') 53 | (, nagare.examples 0.1.0 (/home/apr/projects/nagare/dev/src/nagare/examples)) 54 | 55 | -------------------------------------------------------------------------------- /doc/presentation.txt: -------------------------------------------------------------------------------- 1 | Presentation tier 2 | ================= 3 | 4 | This document describes how to generate HTML and XML dialects with the service 5 | offered by the framework. 6 | 7 | :doc:`components` explains how to associate HTML or XML views to the components. 8 | 9 | Principles 10 | ---------- 11 | 12 | With Nagare, to generate a HTML text, you always build a tree of objects in 13 | memory, a Document Objects Model (not the DOM version of the W3C), that you 14 | can serialize in a (X)HTML text. 15 | 16 | One benefic effect of this serialization is that the generated HTML or XHTML 17 | is always valid. 18 | 19 | The framework offers to you three ways to generate the DOM: 20 | 21 | 1. functional 22 | 2. imperative 23 | 3. templating 24 | 25 | It's important to note that these three methods all generate a DOM and so can 26 | be freely mixed to build an aggregating DOM. 27 | 28 | DOM element creation 29 | -------------------- 30 | 31 | A ``Renderer`` is a DOM element factory. For example, the XHTML renderer 32 | :class:`nagare.renderers.xhtml.Renderer` has an attribute, for 33 | each XHTML tag, that create a DOM element: 34 | 35 | .. code-block:: pycon 36 | 37 | >>> from nagare.renderers import xhtml 38 | >>> h = xhtml.Renderer() 39 | >>> h.div 40 | 41 | 42 | DOM elements API 43 | ---------------- 44 | 45 | ElementTree API 46 | ~~~~~~~~~~~~~~~ 47 | 48 | A DOM element is an instance of the class :class:`nagare.renderers.xml._Tag`. 49 | As this class inherits from ``lxml.etree.Element``, the DOM element offers an 50 | `ElementTree API. `_: 51 | 52 | .. code-block:: pycon 53 | 54 | >>> from nagare.renderers import xhtml 55 | >>> h = xhtml.Renderer() 56 | 57 | >>> root = h.div 58 | >>> root.set('id', 'content') 59 | 60 | >>> title = h.h1 61 | >>> title.text = 'Hello world' 62 | 63 | >>> root.append(title) 64 | >>> root.write_htmlstring() 65 | '

Hello world

' 66 | 67 | Lxml API 68 | ~~~~~~~~ 69 | 70 | You can also `validate `_ a DOM tree 71 | against a DTD, XML Schema, RelaxNG or Schematron document. 72 | 73 | And you can use `XPath and Xsl transformations `_. 74 | 75 | Functional DOM building API 76 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~ 77 | 78 | The ``_Tag`` class implements a ``__call__`` method to add children, text or 79 | attributes to a DOM element: 80 | 81 | .. code-block:: python 82 | 83 | def __call__(self, *children, **attributes) 84 | 85 | Each element in ``children`` can be a: 86 | 87 | - DOM element that is added as a child of the DOM element 88 | - string, unicode string, integer or float that is converted to unicode 89 | string and added as data to the DOM element 90 | - list, tuple or generator which each elements are added to the DOM element 91 | - dictionary which each items are added as attributes of the DOM element 92 | - :doc:`component ` ``comp`` wich will be rendered with its 93 | default view (i.e ``comp.render(h)``) and the resulting DOM tree added 94 | to the DOM element 95 | 96 | Each items in ``attributes`` are added as attributes of the DOM element. 97 | 98 | .. note:: 99 | 100 | As a keyword parameter cannot have the name of a Python reserved keyword, 101 | add a ``_`` character to the name (i.e: ``h.div(class_='content')``) 102 | 103 | Using the DOM element creation API of the renderers, with the nesting of 104 | calls to the created DOM element, a DOM tree can be built in a functional way: 105 | 106 | .. code-block:: python 107 | 108 | >>> from nagare.renderers import xhtml 109 | >>> h = xhtml.Renderer() 110 | 111 | >>> root = h.div(h.h1('Hello world'), id='content') 112 | >>> root.write_htmlstring() 113 |

Hello world

' 114 | 115 | Imperative DOM building API 116 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~ 117 | 118 | A DOM element is also a `context manager `_ 119 | 120 | Once a context is opened using the ``with`` statement, children can be added 121 | to the context manager using the ``<<`` operator on the renderer. 122 | 123 | So a DOM tree can also be imperatively built from the nesting of contexts. With its root 124 | finally retrieved from the attribute ``root`` of the renderer: 125 | 126 | .. code-block:: python 127 | 128 | >>> from nagare.renderers import xhtml 129 | >>> h = xhtml.Renderer() 130 | 131 | >>> with h.div: 132 | ... h << { 'id' : 'content' } 133 | ... with h.h1: 134 | ... h << 'Hello world' 135 | 136 | >>> root = h.root 137 | >>> root.write_htmlstring() 138 | '

Hello world

' 139 | 140 | .. note:: 141 | 142 | In a component view, don't forget to return the tree with ``h.root`` 143 | 144 | Remember that the different ways to build a DOM tree can be freely mixed, 145 | so the example above can be rewrote as: 146 | 147 | .. code-block:: python 148 | 149 | >>> from nagare.renderers import xhtml 150 | >>> h = xhtml.Renderer() 151 | 152 | >>> with h.div(id='content'): 153 | ... h << h.h1('Hello world') 154 | 155 | >>> root = h.root 156 | >>> root.write_htmlstring() 157 | '

Hello world

' 158 | 159 | 160 | Or, to create a HTML list from the Python list ``l``: 161 | 162 | .. code-block:: python 163 | 164 | # Functional 165 | >>> root = h.ul([u.li(element) for element in l]) 166 | 167 | # Imperative 168 | >>> with h.ul: 169 | ... for element in l: 170 | ... h << h.ul(element) 171 | >>> root = h.root 172 | 173 | Template 174 | ~~~~~~~~ 175 | 176 | The templating system in Nagare follows the idea of the 177 | *meld family*: the designer who creates the XHTML template only gives identifiers to nodes. 178 | Then the developer can parse the template into a DOM tree, search for the DOM 179 | elements given their identifiers and manipulates them in Python. 180 | 181 | 1. Creating the template 182 | ++++++++++++++++++++++++ 183 | 184 | The template must be a valid XML document, for example a XHTML template and 185 | the namespace ``http://www.plope.com/software/meld3`` must be declared. 186 | 187 | To mark the nodes, use the attribute ``id`` of this namespace: 188 | 189 | .. code-block:: html 190 | 191 | 192 | 193 | 195 | 196 |

Hello

197 | 198 | 199 | 200 | The Python helper method ``meld_id()`` is available to add a ``meld:id`` 201 | attribute to a DOM element: 202 | 203 | .. code-block:: pycon 204 | 205 | >>> from nagare.renderers import xhtml 206 | >>> h = xhtml.Renderer() 207 | 208 | >>> tree = h.html( 209 | ... h.body( 210 | ... h.h1('Hello').meld_id('title') 211 | ... ) 212 | ... ) 213 | >>> print tree.write_xmlstring(pretty_print=True) 214 | 215 | 216 |

Hello

217 | 218 | 219 | 220 | 2. Parsing the template 221 | +++++++++++++++++++++++ 222 | 223 | You can use the ``parse_htmlstring()`` method of a xhtml renderer to parse a 224 | template: 225 | 226 | .. code-block:: pycon 227 | 228 | >>> from nagare.renderers import xhtml 229 | >>> h = xhtml.Renderer() 230 | 231 | >>> root = h.parse_html('/tmp/template.xml', xhtml=True) 232 | 233 | >>> print root.write_xmlstring() 234 | 235 | 236 |

Hello

237 | 238 | 239 | 240 | 3. Finding the DOM elements 241 | +++++++++++++++++++++++++++ 242 | 243 | Use the ``findmeld()`` method of the DOM elements to retreive the marked nodes: 244 | 245 | .. code-block:: pycon 246 | 247 | >>> from nagare.renderers import xhtml 248 | >>> h = xhtml.Renderer() 249 | 250 | >>> root = h.parse_html('/tmp/template.xml', xhtml=True) 251 | 252 | >>> root.findmeld('title') 253 | 254 | 255 | 4. Manipulating the DOM elements 256 | ++++++++++++++++++++++++++++++++ 257 | 258 | Once a DOM element is found, you can manipulate it with the normal ElementTree, 259 | Lxml, functional or imperative API: 260 | 261 | .. code-block:: pycon 262 | 263 | >>> from nagare.renderers import xhtml 264 | >>> h = xhtml.Renderer() 265 | 266 | >>> root = h.parse_html('/tmp/template.xml', xhtml=True) 267 | 268 | >>> t = root.findmeld('title') 269 | >>> t.text = 'World' 270 | 271 | >>> print root.write_xmlstring() 272 | 273 | 274 |

World

275 | 276 | 277 | 278 | 279 | These three methods of the DOM elements are helpers to work with templates: 280 | 281 | - ``fill(self, *children, **attributes)`` -- replaces the children of the 282 | DOM element by the given children and appends the ``attributes`` to the DOM 283 | element current attributes. 284 | 285 | .. code-block:: python 286 | 287 | >>> from nagare.renderers import xhtml 288 | >>> h = xhtml.Renderer() 289 | 290 | >>> root = h.div(h.h1('Hello world'), id='content') 291 | >>> root.write_htmlstring() 292 |

Hello world

' 293 | 294 | >>> root.fill('Go to ', h.a('Nagare home', href='http://www.nagare.org'), class_='description') 295 | 296 | >>> root.write_htmlstring() 297 | '' 298 | 299 | - ``replace(self, *children)`` -- replace the DOM element by the given children: 300 | 301 | .. code-block:: python 302 | 303 | >>> from nagare.renderers import xhtml 304 | >>> h = xhtml.Renderer() 305 | 306 | >>> root = h.div(h.h1('Hello world'), id='content') 307 | >>> root.write_htmlstring() 308 |

Hello world

' 309 | >>> root[0].write_htmlstring() 310 |

Hello world

311 | 312 | >>> root[0].replace(h.h2('Nagare')) 313 | 314 | >>> root.write_htmlstring() 315 | '

Nagare

' 316 | 317 | - ``repeat(self, iterable, childname=None)`` 318 | 319 | Repeats an element with values from an iterable. 320 | 321 | If ``childname`` is not ``None``, repeat the element on which repeat was called, 322 | otherwise find the child element with a ``meld:id`` matching ``childname`` 323 | and repeat that. The element is repeated within its parent element. 324 | 325 | This method returns an iterable; the value of each iteration is a 326 | two-sequence in the form (newelement, data). ``newelement`` is a clone of 327 | the template element (including clones of its children) which has already 328 | been seated in its parent element in the template. ``data`` is a value from 329 | the passed in iterable. Changing ``newelement`` (typically based on values 330 | from ``data``) mutates the element "in place". 331 | 332 | .. code-block:: pycon 333 | 334 | >>> from nagare.renderers import xhtml 335 | >>> h = xhtml.Renderer() 336 | 337 | >>> root = h.parse_xmlstring('''
    338 | ...
  • A line example
  • 339 | ...
''') 340 | 341 | >>> items = ['Item %d' % i for i in range(3)] 342 | 343 | >>> for (element, item) in root.findmeld('alist').repeat(items): 344 | ... element.text = item 345 | ... 346 | 347 | >>> print root.write_xmlstring() 348 |
    349 |
  • Item 0
  • 350 |
  • Item 1
  • 351 |
  • Item 2
  • 352 |
353 | 354 | DOM tree serialization 355 | ~~~~~~~~~~~~~~~~~~~~~~ 356 | 357 | Once a tree of DOM elements is build, it can be serialized in (X)HTML: 358 | 359 | - ``write_xmlstring(self, encoding='utf-8', pipeline=True)`` -- serializes 360 | the DOM tree into a XML string, encoded according to the ``encoding`` 361 | parameter. If ``pipeline`` is True, the eventual ``meld:id`` attributes are 362 | removed, else they are kept so that the xml string can be re-parse as a 363 | template. 364 | 365 | - ``write_htmlstring(self, encoding='utf-8', pipeline=True)`` -- serializes 366 | the DOM tree into a HTML string, encoded according to the ``encoding`` 367 | parameter. If ``pipeline`` is True, the eventual ``meld:id`` attributes are 368 | removed. 369 | 370 | -------------------------------------------------------------------------------- /doc/publisher_file.txt: -------------------------------------------------------------------------------- 1 | The publisher configuration file 2 | ================================ 3 | 4 | 5 | Using the configuration file 6 | ---------------------------- 7 | 8 | To configure how an application is published, use the ``-c`` option of the 9 | ``serve`` administrative command: 10 | 11 | .. code-block:: sh 12 | 13 | /bin/nagare-admin serve -c /path/to/the/publisher/config.cfg 14 | 15 | Several examples of publisher configurations are located into ``conf/publishers`` 16 | under the framework installation directory. 17 | 18 | Structure 19 | --------- 20 | 21 | For the boolean parameters, a value of ``true``, ``yes``, ``on`` or ``1`` means True 22 | and a value of ``false``, ``no``, ``off`` or ``0`` mean False. 23 | 24 | Comments start with the ``#`` character. 25 | 26 | [publisher] section 27 | ~~~~~~~~~~~~~~~~~~~ 28 | 29 | This section defines the publisher to use. 30 | 31 | =================== ========= ================== ================================================== 32 | Name Mandatory Default value Description 33 | =================== ========= ================== ================================================== 34 | type No standalone Choice of the publisher to use. 35 | 36 | The current choices are: 37 | 38 | - ``standalone``: Python threaded HTTP server 39 | (convenient in development mode) 40 | - ``fastcgi``: multi-processes fastcgi server. 41 | A external HTTP is required (recommended in 42 | production) 43 | - ``fapws3``: fast event-driven HTTP server 44 | (experimental) 45 | - ``eventlet``: fast event-driven HTTP server 46 | (experimental) 47 | host No 127.0.0.1 By default, the publisher only accepts requests 48 | on the local interface. If you want to accept 49 | external requests, set this parameter to the 50 | internet address of your external interface or 51 | to '0.0.0.0' to listen to on all the interfaces 52 | of your system. 53 | The optional ``--host`` on the command line 54 | takes precedence over this parameter. 55 | port No *publisher Port where to listen to the requests. If no 56 | dependent* value is explicitly given, the ``standalone``, 57 | ``fapws3`` and ``eventlet`` publishers listen on 58 | port ``8080``. And the ``fastcgi`` publisher 59 | listen on ``9000``. 60 | The optional ``--port / -p`` on the command line 61 | takes precedence over this parameter. 62 | debug No off Put the publisher in debug mode 63 | =================== ========= ================== ================================================== 64 | 65 | If the application is publisher by the FastCGI publisher (i.e 66 | ``type=fastcgi``), these parameters can also be set: 67 | 68 | =================== ========= ================== ================================================== 69 | Name Mandatory Default value Description 70 | =================== ========= ================== ================================================== 71 | socket No *No default value* Unix socket path instead of the TCP host:port 72 | socket 73 | umask No *No default value* Unix permission mask of the unix socket to create 74 | minSpare No 1 Minimum number of processes ready to process new 75 | requests 76 | maxChildren No 50 Maximum number of running processes 77 | maxSpare No 5 Maximum number of idle processes 78 | maxRequest No *No default value* Maximum number of requests served by a process 79 | before being killed 80 | =================== ========= ================== ================================================== 81 | 82 | .. note:: 83 | 84 | New publishers can be added to the framework, and then selected with the ``type`` 85 | parameter, by registering them under the ``[nagare.publishers]`` :doc:`entry point `. 86 | 87 | [sessions] section 88 | ~~~~~~~~~~~~~~~~~~ 89 | 90 | This section defined the sessions manager to use. 91 | 92 | =================== ========= ================== ================================================== 93 | Name Mandatory Default value Description 94 | =================== ========= ================== ================================================== 95 | type No standalone Choice of the sessions manager to use. 96 | 97 | The current choices are: 98 | 99 | - ``standalone``: threads-safe in-memory sessions 100 | manager. Can be used with the ``standalone``, 101 | ``fapws3`` or ``eventlet`` publisher. 102 | - ``memcache``: the sessions are stored and 103 | shared into an external memcached server. 104 | Can be use will all the publishers 105 | =================== ========= ================== ================================================== 106 | 107 | If the ``type`` parameter has the value ``standalone``, the following parameters 108 | can be configured: 109 | 110 | =================== ========= ================== ================================================== 111 | Name Mandatory Default value Description 112 | =================== ========= ================== ================================================== 113 | nb No 10000 Maximum number of sessions keeped 114 | =================== ========= ================== ================================================== 115 | 116 | If the ``type`` parameter has the value ``memcache``, the following parameters 117 | can be configured: 118 | 119 | =================== ========= ================== ================================================== 120 | Name Mandatory Default value Description 121 | =================== ========= ================== ================================================== 122 | host No 127.0.0.1 Address of the memcached server 123 | port No 11211 Port of the memcached server 124 | ttl No 0 How long (in seconds) does the session live? 125 | A value of ``0`` means the sessions are managed 126 | in LRU. 127 | reset No on If this parameter is true, then all the sessions 128 | are removed from the memcached server when the 129 | application (re)starts. 130 | debug No off Display the requests sent to the memcached server 131 | =================== ========= ================== ================================================== 132 | 133 | .. note:: 134 | 135 | New sessions managers can be added to the framework, and then selected with the 136 | ``type`` parameter, by registering them under the ``[nagare.sessions]`` 137 | :doc:`entry point `. 138 | 139 | [reloader] section 140 | ~~~~~~~~~~~~~~~~~~ 141 | 142 | This section configures the reloader process which automatically detect source 143 | changes and relaunch the application. 144 | 145 | =================== ========= ================== ================================================== 146 | Name Mandatory Default value Description 147 | =================== ========= ================== ================================================== 148 | activated No off If not activated, the framework will not read 149 | the following parameters. 150 | In production, don't activate the reloader. 151 | interval No 1 Time, in seconds, between the control of 152 | source changes 153 | =================== ========= ================== ================================================== 154 | 155 | -------------------------------------------------------------------------------- /doc/quickstart.txt: -------------------------------------------------------------------------------- 1 | QuickStart 2 | ========== 3 | 4 | This document describes the standard installation of the Nagare framework 5 | on Linux and how to start your first application. To install it on Windows, 6 | read :doc:`framework_installation`. 7 | 8 | 1. Stackless Python installation 9 | -------------------------------- 10 | 11 | The Nagare framework uses Stackless Python (version 2.5.2 or above). So, grab 12 | the latest version of 13 | `Stackless Python `_ 14 | for your platform and install it. 15 | 16 | For example, to install Stackless Python in the ** directory, 17 | on Linux: 18 | 19 | .. code-block:: sh 20 | 21 | cd /tmp 22 | wget http://www.stackless.com/binaries/stackless-2714-export.tar.xz 23 | tar xvf stackless-2714-export.tar.xz 24 | 25 | cd stackless-2714-export 26 | ./configure --prefix= 27 | make all 28 | make install 29 | 30 | 2. Nagare installation 31 | ---------------------- 32 | 33 | The following two steps install the Nagare framework and all its dependences into 34 | the ** directory of your choice. 35 | 36 | 2.1 Create a virtual environment 37 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 38 | 39 | Download ``virtualenv`` and create a virtual environment into the ** 40 | directory **with Stackless Python**: 41 | 42 | .. code-block:: sh 43 | 44 | cd /tmp 45 | wget https://pypi.python.org/packages/d4/0c/9840c08189e030873387a73b90ada981885010dd9aea134d6de30cd24cb8/virtualenv-15.1.0.tar.gz 46 | tar xvf virtualenv-15.1.0.tar.gz 47 | 48 | /bin/python virtualenv-15.1.0/virtualenv.py 49 | 50 | 2.2 Install the framework 51 | ~~~~~~~~~~~~~~~~~~~~~~~~~ 52 | 53 | Install the whole framework and all its dependencies into with 54 | ``easy_install``: 55 | 56 | .. code-block:: sh 57 | 58 | /bin/easy_install 'nagare[full]' 59 | 60 | 3. Create your first application 61 | -------------------------------- 62 | 63 | To create the application ``my_first_app``, in the current directory: 64 | 65 | 1. create the application skeleton: 66 | 67 | .. code-block:: sh 68 | 69 | /bin/nagare-admin create-app my_first_app 70 | 71 | 2. register your new application to the framework: 72 | 73 | .. code-block:: sh 74 | 75 | cd my_first_app; /bin/python setup.py develop 76 | 77 | 3. launch it: 78 | 79 | .. code-block:: sh 80 | 81 | /bin/nagare-admin serve my_first_app 82 | 83 | Congratulations your first application is available at http://localhost:8080/my_first_app. 84 | 85 | The source code of your application is in ``my_first_app/my_first_app/app.py``, 86 | ready to be modified ! 87 | 88 | -------------------------------------------------------------------------------- /doc/renderer.txt: -------------------------------------------------------------------------------- 1 | The Renderer objects 2 | ==================== 3 | 4 | Creating a renderer 5 | ------------------- 6 | 7 | As described in :doc:`components`, the views of a component receives 8 | a renderer. A renderer is a DOM objects factory that the developer uses to 9 | build the DOM tree of the :doc:`view `: 10 | 11 | .. code-block:: python 12 | 13 | from nagare import presentation 14 | 15 | class App(object): 16 | pass 17 | 18 | @presentation.render_for(App): 19 | def render(self, h, *args): 20 | # the `h` parameter is the renderer, used to generate a DOM tree 21 | return h.div(h.h1('Hello world!')) 22 | 23 | By default, each view receives a new HTML renderer 24 | (instance of :class:`nagare.renderers.html.Renderer`) suitable to build a 25 | synchronous HTML DOM tree. 26 | 27 | A single view can also use a different renderer, for example to generate 28 | another XML dialect than HTML: 29 | 30 | .. code-block:: python 31 | 32 | from nagare import presentation 33 | from nagare.renderers import xml 34 | 35 | class App(object): 36 | pass 37 | 38 | @presentation.render_for(App): 39 | def render(self, h, *args): 40 | h.response.content_type = 'text/xml' 41 | 42 | x = xml.Renderer(h) # Creating a XML renderer 43 | return x.contact(x.name('John Doe'), x.age(20)) 44 | 45 | or to generate an asynchronous view of a component: 46 | 47 | .. code-block:: python 48 | 49 | from nagare import presentation, component 50 | from nagare.renderers import xhtml 51 | from MyBlog import Blog 52 | 53 | class App: 54 | def __init__(self): 55 | self.content = component.Component(Blog()) 56 | 57 | @presentation.render_for(App): 58 | def render(self, h, *args): 59 | # Creating an asynchronous XHTML renderer 60 | a = h.AsyncRenderer() 61 | return h.div(self.content.render(a)) 62 | 63 | .. note:: 64 | 65 | The new renderer must be created with the current renderer as first parameter, 66 | to maintain a parent/child relationship between them. 67 | 68 | Now, if all your views need to generate XML (or use any other renderer), it would be 69 | cumbersome to create a renderer in each view. Instead, you can tell Nagare to use 70 | another default renderer by setting the ``renderer_factory`` attribute of the 71 | :doc:`WSGIApp `, as shown in the example below: 72 | 73 | .. code-block:: python 74 | 75 | from nagare import wsgi 76 | from nagare.renderers import xml 77 | 78 | class WSGIApp(wsgi.WSGIApp): 79 | # use the XML renderer by default 80 | renderer_factory = xml.Renderer 81 | 82 | Then, all the views will receive an instance of ``xml.Renderer`` as renderer. Note that 83 | you can still create another renderer in the view as explained above. 84 | 85 | 86 | Default renderers 87 | ----------------- 88 | 89 | HTML renderer 90 | ~~~~~~~~~~~~~ 91 | 92 | A HTML renderer is an instance of :class:`nagare.renderers.html.Renderer`. 93 | 94 | A HTML renderer is a factory for all the possible HTML tags. It also has: 95 | 96 | - a ``request`` and a ``response`` attributes which are WebOb 97 | `request `_ 98 | and 99 | `response `_ 100 | objects. 101 | 102 | - a ``head`` attribute which is a Head renderer instance, see below. 103 | 104 | - the following methods: 105 | 106 | - ``parse_html(self, source, fragment=False, no_leading_text=False, xhtml=False)`` -- 107 | to read HTML or XHTML (chosen with the ``xhtml`` parameter) from the 108 | ``source`` file to a DOM tree. If ``fragment`` is ``True``, the (X)HTML 109 | read can have multiple roots and the result will always be a list of DOM 110 | trees. Also, if ``fragment`` is ``True``, if ``no_leading_text`` is 111 | ``True`` and the HTML begins by a text instead of a node, this text 112 | is removed. 113 | 114 | - ``parse_htmlstring(self, text, fragment=False, no_leading_text=False, xhtml=False)`` -- 115 | as ``parse_html()`` but the HTML is read from the ``text`` string. 116 | 117 | Head renderer 118 | ~~~~~~~~~~~~~ 119 | 120 | The ``head`` attribute of a HTML renderer is a Head renderer, an instance 121 | of :class:`nagare.renderers.html.HeadRenderer`. 122 | 123 | A head renderer is a factory for all the HTML tags that can be embedded into 124 | the ```` section: ``base``, ``head``, ``link``, ``meta``, ``title``, 125 | ``style`` and ``script``: 126 | 127 | .. code-block:: python 128 | 129 | from nagare import presentation 130 | 131 | class App: 132 | pass 133 | 134 | @presentation.render_for(App): 135 | def render(self, h, *args): 136 | # h.head is a Head renderer 137 | h.head << h.head.title('My Application') 138 | 139 | return h.div(h.h1('Hello world !')) 140 | 141 | A head renderer has also the following method: 142 | 143 | - ``css(self, name, style)`` -- add a css style definition. The style must 144 | be named so that, if added by several views, it will be included only 145 | once in the generated page. 146 | 147 | .. code-block:: python 148 | 149 | @presentation.render_for(App): 150 | def render(self, h, *args): 151 | h.head.css('main_content', '.main { border: 1px red solid }') 152 | 153 | return h.div(h.h1('Hello world!'), class_='main') 154 | 155 | - ``css_url(self, url)`` -- add a css url. If the url is relative, it's 156 | relative to the ``static`` directory of the :doc:`application `. 157 | 158 | .. code-block:: python 159 | 160 | @presentation.render_for(App): 161 | def render(self, h, *args): 162 | h.head.css_url('http://www.nagare.org/site.css') # Absolute URL 163 | h.head.css_url('css/my_application.css') # Relative URL 164 | 165 | return h.div(h.h1('Hello world!'), class_='main') 166 | 167 | - ``javascript(self, name, script)`` -- add a javascript definition. 168 | The javascript code must be named so that, if added by several views, it 169 | will be included only once in the generated page. 170 | 171 | .. code-block:: python 172 | 173 | @presentation.render_for(App): 174 | def render(self, h, *args): 175 | h.head.javascript('debug', 'function debug(msg) { alert(msg) }') 176 | 177 | return h.div(h.h1('Hello world!'), class_='main') 178 | 179 | - ``javascript_url(self, url)`` -- add a javascript url. If the url is 180 | relative, it's relative to the ``static`` directory of the 181 | :doc:`application ` 182 | 183 | .. code-block:: python 184 | 185 | @presentation.render_for(App): 186 | def render(self, h, *args): 187 | h.head.javascript_url('http://www.nagare.org/anim.js') # Absolute URL 188 | h.head.javascript_url('js/debug.js') # Relative URL 189 | 190 | return h.div(h.h1('Hello world!'), class_='main') 191 | 192 | After the rendering phase, Nagare generates a ```` section which is the 193 | concatenation of all the DOM objects the different views have put into the 194 | Head renderer. 195 | 196 | HTML5 renderer 197 | ~~~~~~~~~~~~~~ 198 | 199 | A HTML5 renderer is an instance of :class:`nagare.renderers.html5.Renderer`. 200 | 201 | A HTML5 renderer is a factory for all the possible HTML5 from the `HTML5 specification`_. 202 | 203 | Since it's extremely likely that you want to use this renderer in all the views 204 | of your application instead of the default HTML renderer, you can install it 205 | like this: 206 | 207 | .. code-block:: python 208 | 209 | from nagare import wsgi 210 | from nagare.renderers import xhtml5 211 | 212 | class WSGIApp(wsgi.WSGIApp): 213 | # use the HTML5 renderer by default 214 | renderer_factory = xhtml5.Renderer 215 | 216 | Then, all the views will receive a HTML5 renderer by default. 217 | 218 | This renderer is like an HTML renderer except that the set of available tags and 219 | attributes is a little different. For example, HTML5 offers ``