├── .github
├── FUNDING.yml
├── dependabot.yml
└── workflows
│ ├── lint.yml
│ ├── publish.yml
│ └── tests.yml
├── .gitignore
├── BACKERS.md
├── CHANGES.md
├── LICENSE
├── Makefile
├── README.md
├── artwork
├── LICENSE
├── logo-bwb-wb-sc.png
├── logo-bwb-xb-sc.png
└── logo-bwb-xb-xl.png
├── docs
├── app_and_modules.md
├── auth.md
├── caching.md
├── cli.md
├── debug_and_logging.md
├── deployment.md
├── extensions.md
├── foreword.md
├── forms.md
├── html.md
├── installation.md
├── languages.md
├── mailer.md
├── orm.md
├── orm
│ ├── advanced.md
│ ├── callbacks.md
│ ├── connecting.md
│ ├── migrations.md
│ ├── models.md
│ ├── operations.md
│ ├── relations.md
│ ├── scopes.md
│ └── virtuals.md
├── patterns.md
├── pipeline.md
├── quickstart.md
├── request.md
├── response.md
├── routing.md
├── services.md
├── sessions.md
├── templates.md
├── testing.md
├── tree.yml
├── tutorial.md
├── upgrading.md
├── validations.md
└── websocket.md
├── emmett
├── __init__.py
├── __main__.py
├── __version__.py
├── _internal.py
├── _reloader.py
├── _shortcuts.py
├── app.py
├── asgi
│ ├── __init__.py
│ ├── handlers.py
│ └── wrappers.py
├── assets
│ ├── __init__.py
│ ├── debug
│ │ ├── __init__.py
│ │ ├── shBrushPython.js
│ │ ├── shCore.css
│ │ ├── shCore.js
│ │ ├── shTheme.css
│ │ ├── view.css
│ │ └── view.html
│ ├── helpers.js
│ ├── jquery.min.js
│ └── jquery.min.map
├── cache.py
├── cli.py
├── ctx.py
├── datastructures.py
├── debug.py
├── extensions.py
├── forms.py
├── helpers.py
├── html.py
├── http.py
├── language
│ ├── __init__.py
│ ├── helpers.py
│ └── translator.py
├── libs
│ ├── __init__.py
│ ├── contenttype.py
│ └── portalocker.py
├── locals.py
├── orm
│ ├── __init__.py
│ ├── _patches.py
│ ├── adapters.py
│ ├── apis.py
│ ├── base.py
│ ├── connection.py
│ ├── engines
│ │ ├── __init__.py
│ │ ├── postgres.py
│ │ └── sqlite.py
│ ├── errors.py
│ ├── geo.py
│ ├── helpers.py
│ ├── migrations
│ │ ├── __init__.py
│ │ ├── base.py
│ │ ├── commands.py
│ │ ├── engine.py
│ │ ├── exceptions.py
│ │ ├── generation.py
│ │ ├── helpers.py
│ │ ├── migration.tmpl
│ │ ├── operations.py
│ │ ├── revisions.py
│ │ ├── scripts.py
│ │ └── utils.py
│ ├── models.py
│ ├── objects.py
│ ├── transactions.py
│ └── wrappers.py
├── parsers.py
├── pipeline.py
├── routing
│ ├── __init__.py
│ ├── response.py
│ ├── router.py
│ ├── routes.py
│ ├── rules.py
│ └── urls.py
├── rsgi
│ ├── __init__.py
│ ├── handlers.py
│ └── wrappers.py
├── security.py
├── serializers.py
├── sessions.py
├── templating
│ ├── __init__.py
│ ├── lexers.py
│ └── templater.py
├── testing.py
├── tools
│ ├── __init__.py
│ ├── auth
│ │ ├── __init__.py
│ │ ├── apis.py
│ │ ├── exposer.py
│ │ ├── ext.py
│ │ ├── forms.py
│ │ └── models.py
│ ├── decorators.py
│ ├── mailer.py
│ ├── service.py
│ └── stream.py
├── utils.py
├── validators
│ ├── __init__.py
│ ├── basic.py
│ ├── consist.py
│ ├── helpers.py
│ ├── inside.py
│ └── process.py
└── wrappers
│ ├── __init__.py
│ ├── request.py
│ ├── response.py
│ └── websocket.py
├── examples
└── bloggy
│ ├── app.py
│ ├── migrations
│ └── 9d6518b3cdc2_first_migration.py
│ ├── static
│ └── style.css
│ ├── templates
│ ├── auth
│ │ └── auth.html
│ ├── index.html
│ ├── layout.html
│ ├── new_post.html
│ └── one.html
│ └── tests.py
├── pyproject.toml
└── tests
├── helpers.py
├── languages
├── de.json
├── it.json
└── ru.json
├── templates
├── auth
│ └── auth.html
├── layout.html
└── test.html
├── test_auth.py
├── test_cache.py
├── test_logger.py
├── test_mailer.py
├── test_migrations.py
├── test_orm.py
├── test_orm_connections.py
├── test_orm_gis.py
├── test_orm_pks.py
├── test_orm_row.py
├── test_orm_transactions.py
├── test_pipeline.py
├── test_routing.py
├── test_templates.py
├── test_translator.py
├── test_utils.py
├── test_validators.py
└── test_wrappers.py
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: [gi0baro]
2 | polar: emmett-framework
3 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: "github-actions"
4 | target-branch: "master"
5 | directory: "/"
6 | schedule:
7 | interval: "monthly"
8 |
--------------------------------------------------------------------------------
/.github/workflows/lint.yml:
--------------------------------------------------------------------------------
1 | name: lint
2 |
3 | on:
4 | pull_request:
5 | types: [opened, synchronize]
6 | branches:
7 | - master
8 |
9 | env:
10 | PYTHON_VERSION: 3.12
11 |
12 | jobs:
13 | lint:
14 | runs-on: ubuntu-latest
15 |
16 | steps:
17 | - uses: actions/checkout@v4
18 | - name: Set up Python ${{ env.PYTHON_VERSION }}
19 | uses: actions/setup-python@v5
20 | with:
21 | python-version: ${{ env.PYTHON_VERSION }}
22 | - name: Install uv
23 | uses: astral-sh/setup-uv@v5
24 | - name: Install dependencies
25 | run: |
26 | uv sync --dev
27 | - name: Lint
28 | run: |
29 | source .venv/bin/activate
30 | make lint
31 |
--------------------------------------------------------------------------------
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------
1 | name: Publish
2 |
3 | on:
4 | release:
5 | types: [published]
6 |
7 | jobs:
8 | publish:
9 | runs-on: ubuntu-latest
10 | environment:
11 | name: pypi
12 | url: https://pypi.org/p/emmett
13 | permissions:
14 | id-token: write
15 |
16 | steps:
17 | - uses: actions/checkout@v4
18 | - name: Set up Python
19 | uses: actions/setup-python@v5
20 | with:
21 | python-version: 3.12
22 | - name: Install uv
23 | uses: astral-sh/setup-uv@v5
24 | - name: Build distributions
25 | run: |
26 | uv build
27 | - name: Publish package to pypi
28 | uses: pypa/gh-action-pypi-publish@release/v1
29 | with:
30 | skip-existing: true
31 |
--------------------------------------------------------------------------------
/.github/workflows/tests.yml:
--------------------------------------------------------------------------------
1 | name: Tests
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | - release
8 | pull_request:
9 | types: [opened, synchronize]
10 | branches:
11 | - master
12 |
13 | jobs:
14 | Linux:
15 | runs-on: ubuntu-latest
16 | strategy:
17 | fail-fast: false
18 | matrix:
19 | python-version: [3.9, '3.10', '3.11', '3.12', '3.13']
20 |
21 | services:
22 | postgres:
23 | image: postgis/postgis:12-3.2
24 | env:
25 | POSTGRES_PASSWORD: postgres
26 | POSTGRES_DB: test
27 | ports:
28 | - 5432:5432
29 |
30 | steps:
31 | - uses: actions/checkout@v4
32 | - name: Set up Python ${{ matrix.python-version }}
33 | uses: actions/setup-python@v5
34 | with:
35 | python-version: ${{ matrix.python-version }}
36 | - name: Install uv
37 | uses: astral-sh/setup-uv@v5
38 | - name: Install dependencies
39 | run: |
40 | uv sync --dev
41 | - name: Test
42 | env:
43 | POSTGRES_URI: postgres:postgres@localhost:5432/test
44 | run: |
45 | uv run pytest -v tests
46 |
47 | MacOS:
48 | runs-on: macos-latest
49 | strategy:
50 | fail-fast: false
51 | matrix:
52 | # FIXME: skipping 3.9 due to issues with `psycopg2-binary`
53 | # python-version: [3.9, '3.10', '3.11', '3.12', '3.13']
54 | python-version: ['3.10', '3.11', '3.12', '3.13']
55 |
56 | steps:
57 | - uses: actions/checkout@v4
58 | - name: Set up Python ${{ matrix.python-version }}
59 | uses: actions/setup-python@v5
60 | with:
61 | python-version: ${{ matrix.python-version }}
62 | - name: Install uv
63 | uses: astral-sh/setup-uv@v5
64 | - name: Install dependencies
65 | run: |
66 | uv sync --dev
67 | - name: Test
68 | run: |
69 | uv run pytest -v tests
70 |
71 | Windows:
72 | runs-on: windows-latest
73 | strategy:
74 | fail-fast: false
75 | matrix:
76 | python-version: [3.9, '3.10', '3.11', '3.12', '3.13']
77 |
78 | steps:
79 | - uses: actions/checkout@v4
80 | - name: Set up Python ${{ matrix.python-version }}
81 | uses: actions/setup-python@v5
82 | with:
83 | python-version: ${{ matrix.python-version }}
84 | - name: Install uv
85 | uses: astral-sh/setup-uv@v5
86 | - name: Install dependencies
87 | run: |
88 | uv sync --dev
89 | - name: Test
90 | shell: bash
91 | run: |
92 | uv run pytest -v tests
93 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.DS_Store
2 | *.pyc
3 | __pycache__
4 |
5 | *.sublime-*
6 | .venv
7 | .vscode
8 |
9 | .mypy_cache
10 | .pytest_cache
11 |
12 | build/*
13 | dist/*
14 | Emmett.egg-info/*
15 | poetry.lock
16 | uv.lock
17 |
18 | examples/*/databases
19 | examples/*/logs
20 | examples/*/private
21 | tests/databases/*
22 | tests/logs/*
23 | tests/private/*
24 |
--------------------------------------------------------------------------------
/BACKERS.md:
--------------------------------------------------------------------------------
1 | # Sponsors & Backers
2 |
3 | Emmett is a BSD-licensed open source project. It's an independent project with its ongoing development made possible entirely thanks to the support by these awesome backers. If you'd like to join them, please consider:
4 |
5 | - [Become a backer or sponsor on Github](https://github.com/sponsors/gi0baro).
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2014 Giovanni Barillari
2 |
3 | Redistribution and use in source and binary forms, with or without
4 | modification, are permitted provided that the following conditions are
5 | met:
6 |
7 | 1. Redistributions of source code must retain the above copyright
8 | notice, this list of conditions and the following disclaimer.
9 |
10 | 2. Redistributions in binary form must reproduce the above copyright
11 | notice, this list of conditions and the following disclaimer in the
12 | documentation and/or other materials provided with the distribution.
13 |
14 | 3. Neither the name of the copyright holder nor the names of its
15 | contributors may be used to endorse or promote products derived from
16 | this software without specific prior written permission.
17 |
18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
21 | PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
24 | TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
25 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
26 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
27 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | .DEFAULT_GOAL := all
2 | pysources = emmett tests
3 |
4 | .PHONY: format
5 | format:
6 | ruff check --fix $(pysources)
7 | ruff format $(pysources)
8 |
9 | .PHONY: lint
10 | lint:
11 | ruff check $(pysources)
12 | ruff format --check $(pysources)
13 |
14 | .PHONY: test
15 | test:
16 | pytest -v tests
17 |
18 | .PHONY: all
19 | all: format lint test
20 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | Emmett is a full-stack Python web framework designed with simplicity in mind.
4 |
5 | The aim of Emmett is to be clearly understandable, easy to be learned and to be
6 | used, so you can focus completely on your product's features:
7 |
8 | ```python
9 | from emmett import App, request, response
10 | from emmett.orm import Database, Model, Field
11 | from emmett.tools import service, requires
12 |
13 | class Task(Model):
14 | name = Field.string()
15 | is_completed = Field.bool(default=False)
16 |
17 | app = App(__name__)
18 | app.config.db.uri = "postgres://user:password@localhost/foo"
19 | db = Database(app)
20 | db.define_models(Task)
21 | app.pipeline = [db.pipe]
22 |
23 | def is_authenticated():
24 | return request.headers.get("api-key") == "foobar"
25 |
26 | def not_authorized():
27 | response.status = 401
28 | return {'error': 'not authorized'}
29 |
30 | @app.route(methods='get')
31 | @requires(is_authenticated, otherwise=not_authorized)
32 | @service.json
33 | async def todo():
34 | page = request.query_params.page or 1
35 | tasks = Task.where(
36 | lambda t: t.is_completed == False
37 | ).select(paginate=(page, 20))
38 | return {'tasks': tasks}
39 | ```
40 |
41 | ## Documentation
42 |
43 | The documentation is available at [https://emmett.sh/docs](https://emmett.sh/docs).
44 | The sources are available under the [docs folder](https://github.com/emmett-framework/emmett/tree/master/docs).
45 |
46 | ## Examples
47 |
48 | The *bloggy* example described in the [Tutorial](https://emmett.sh/docs/latest/tutorial) is available under the [examples folder](https://github.com/emmett-framework/emmett/tree/master/examples).
49 |
50 | ## Status of the project
51 |
52 | Emmett is production ready and is compatible with Python 3.9 and above versions.
53 |
54 | Emmett follows a *semantic versioning* for its releases, with a `{major}.{minor}.{patch}` scheme for versions numbers, where:
55 |
56 | - *major* versions might introduce breaking changes
57 | - *minor* versions usually introduce new features and might introduce deprecations
58 | - *patch* versions only introduce bug fixes
59 |
60 | Deprecations are kept in place for at least 3 minor versions, and the drop is always communicated in the [upgrade guide](https://emmett.sh/docs/latest/upgrading).
61 |
62 | ## How can I help?
63 |
64 | We would be very glad if you contributed to the project in one or all of these ways:
65 |
66 | * Talking about Emmett with friends and on the web
67 | * Adding issues and features requests here on GitHub
68 | * Participating in discussions about new features and issues here on GitHub
69 | * Improving the documentation
70 | * Forking the project and writing beautiful code
71 |
72 | ## License
73 |
74 | Emmett is released under the BSD License.
75 |
76 | However, due to original license limitations, contents under [validators](https://github.com/emmett-framework/emmett/tree/master/emmett/validators) and [libs](https://github.com/emmett-framework/emmett/tree/master/emmett/libs) are included in Emmett under their original licenses. Please check the source code for more details.
77 |
--------------------------------------------------------------------------------
/artwork/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2014 Giovanni Barillari
2 |
3 | This work is licensed under the Creative Commons Attribution-NonCommercial 4.0 International License. To view a copy of this license, visit http://creativecommons.org/licenses/by-nc/4.0/ or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA.
4 |
--------------------------------------------------------------------------------
/artwork/logo-bwb-wb-sc.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emmett-framework/emmett/8f206515d0a7b01bf399656383c0b0019ce90065/artwork/logo-bwb-wb-sc.png
--------------------------------------------------------------------------------
/artwork/logo-bwb-xb-sc.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emmett-framework/emmett/8f206515d0a7b01bf399656383c0b0019ce90065/artwork/logo-bwb-xb-sc.png
--------------------------------------------------------------------------------
/artwork/logo-bwb-xb-xl.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emmett-framework/emmett/8f206515d0a7b01bf399656383c0b0019ce90065/artwork/logo-bwb-xb-xl.png
--------------------------------------------------------------------------------
/docs/cli.md:
--------------------------------------------------------------------------------
1 | Command Line Interface
2 | ======================
3 |
4 | Emmett provides a built-in integration of the [click](http://click.pocoo.org) command line interface, to implement and allow customization of command line scripts.
5 |
6 | Basic Usage
7 | -----------
8 |
9 | Emmett automatically installs a command `emmett` inside your virtualenv. The way this helper works is by providing access to all the commands on your Emmett application's instance, as well as some built-in commands that are included out of the box. Emmett extensions can also register more commands there if they desire to do so.
10 |
11 | For the `emmett` command to work, an application needs to be discovered. Emmett tries to automatic discover your application in the current working directory. In case Emmett fails to automatically detect your application, you can tell Emmett which application it should inspect, use the `--app` / `-a` parameter. It should be the import path for your application or the path to a Python file.
12 |
13 | Given that, to run a development server for your application, you can just write in your command line:
14 |
15 | ```bash
16 | > emmett develop
17 | ```
18 |
19 | or, in the case of a single-file app:
20 |
21 | ```bash
22 | > emmett -a myapp.py develop
23 | ```
24 |
25 | Running a Shell
26 | ---------------
27 |
28 | To run an interactive Python shell, you can use the `shell` command:
29 |
30 | ```bash
31 | > emmett shell
32 | ```
33 |
34 | This will start up an interactive Python shell, setup the correct application context and setup the local variables in the shell. By default, you have access to your `app` object, and all the variables you defined in your application module.
35 |
36 | Custom Commands
37 | ---------------
38 |
39 | If you want to add more commands to the shell script, you can do this easily.
40 | In fact, if you want a shell command to setup your application, you can write:
41 |
42 | ```python
43 | from emmett import App
44 |
45 | app = App(__name__)
46 |
47 | @app.command('setup')
48 | def setup():
49 | # awesome code to initialize your app
50 | ```
51 |
52 | The command will then be available on the command line:
53 |
54 | ```bash
55 | > emmett setup
56 | ```
57 |
58 | ### Command groups
59 |
60 | *New in version 2.2*
61 |
62 | You might also want to define several commands within the same *logical group*. In this scenario, the `command_group` decorator is what you're looking for:
63 |
64 | ```python
65 | @app.command_group('tasks')
66 | def tasks_cmd():
67 | pass
68 |
69 |
70 | @tasks_cmd.command('create')
71 | def tasks_create_cmd():
72 | # some code here
73 | ```
74 |
75 | As you can see we defined a `tasks` command group, and a nested `create` command. We can invoke the upper command using:
76 |
77 | > emmett tasks create
78 |
79 | In case you need more information, please check the [click documentation](https://click.palletsprojects.com/en/7.x/commands/) about commands and groups.
80 |
--------------------------------------------------------------------------------
/docs/debug_and_logging.md:
--------------------------------------------------------------------------------
1 | Debug and logging
2 | =================
3 |
4 | *Errare humanum est*, said Seneca, a long time ago. As humans, sometimes we fail, and, sooner or later, we will see an exception on our applications. Even if the code is 100% correct, we can still get exceptions from time to time. And why? Well, *shit happens*, not only as a consequence of the Finagle's Law – even if he was damn right, wasn't he? – but also because the process of deploying web applications forces us to deal with a long list of involved technologies, everyone of which could fail. Just think about it: the client may fail during the request, your database can be overloaded, a hard-drive on your machine can crash, a library you're using can contain errors, and this goes on and on.
5 |
6 | So, what can we do to face all this necessary complexity?
7 |
8 | Emmett provides two facilities to track and debug errors on your application: a *debugger* for your development process, and a simple logging configuration for your production environment.
9 |
10 | Debugger
11 | --------
12 |
13 | When you run your application with the built-in development server or set your `App.debug` attribute to `True`, Emmett will use its internal debugger when an exception occurs to show you some useful information. What does that look like?
14 |
15 | 
16 |
17 | The debug page contains three sections:
18 |
19 | - the **application traceback**
20 | - the **full traceback**
21 | - the **frames** view
22 |
23 | The difference between the two tracebacks is straightforward: the first is filtered only on your application code, while the second contains the complete trace of what happened – including the framework components, libraries, and so on.
24 |
25 | The third section of the debugger page is called *frames* and inspecting it can tell you a lot about what happened during an exception.
26 |
27 | 
28 |
29 | As you can see, for every step of the full traceback, Emmett collects – when is possible – all the variables' contents and reports them as shown in the above screen.
30 |
31 | > – OK, dude. What happens when I have an error in a template?
32 | > – *the debugger catches them too.*
33 |
34 | 
35 |
36 | The debugger will also try to display the line that generated the exception in templates, complete with the error type. Still, when you forget a `pass` in a template file, it can be impossible to show you the statement that was not *passed*.
37 |
38 | Logging application errors
39 | --------------------------
40 |
41 | When your application runs on production, Emmett – obviously – won't display the debug page, but will collect the full traceback and store it in logs. In fact, with the default configuration, a file called *production.log* will be created in the *logs* folder inside your application folder. It will log every message labeled as *warning* level or more severe.
42 |
43 | But how does Emmett logging works?
44 |
45 | It uses the standard Python logging module, and provides a shortcut that you can use with the `log` attribute of your `App`. This becomes handy when you want to add some messages inside your code, because you can just call:
46 |
47 | ```python
48 | app.log.debug('This is a debug message')
49 | app.log.info('This is an info message')
50 | app.log.warning('This is a warning message')
51 | ```
52 |
53 | Basically, the `log` attribute of your app is a Python `Logger` with some handlers configured. As we said above, Emmett automatically logs exceptions calling your `app.log.exception()`.
54 |
55 | ### Configuring application logs
56 |
57 | You probably want to configure logging for your application to fit your needs. To do that, just use your `app.config` object:
58 |
59 | ```python
60 | from emmett import App, sdict
61 |
62 | app = App(__name__)
63 |
64 | app.config.logging.myfile = sdict(
65 | level="info",
66 | max_size=100*1024*1024
67 | )
68 | ```
69 |
70 | With this example, you will generate a *myfile.log* which will grow to 100MB in size and log all messages with an *info* level or higher. This is the complete list of parameters you can set for a logging file:
71 |
72 | | name | description |
73 | | --- | --- |
74 | | max\_size | max size for the logging files (default `5*1024*1024`) |
75 | | file\_no | number of old files to keep with rotation (default `4`) |
76 | | level | logging level (default `'warning'`) |
77 | | format | format for messages (default `'[%(asctime)s] %(levelname)s in %(module)s: %(message)s'`) |
78 | | on\_app\_debug | tells Emmett to log the messages also when application is in debug mode (default `False`) |
79 |
80 | That's it.
81 |
--------------------------------------------------------------------------------
/docs/deployment.md:
--------------------------------------------------------------------------------
1 | Deployment
2 | ==========
3 |
4 | Depending on your setup and preferences, there are multiple ways to run Emmett applications. In this chapter, we'll try to document the most common ones.
5 |
6 | Included server
7 | ---------------
8 |
9 | *Changed in version 2.7*
10 |
11 | Emmett comes with [Granian](https://github.com/emmett-framework/granian) as its HTTP server. In order to run your application in production you can just use the included `serve` command:
12 |
13 | emmett serve --host 0.0.0.0 --port 80
14 |
15 | You can inspect all the available options of the `serve` command using the `--help` option. Here is the full list:
16 |
17 | | option | default | description |
18 | | --- | --- | --- |
19 | | host | 0.0.0.0 | Bind address |
20 | | port | 8000 | Bind port |
21 | | workers | 1 | Number of worker processes |
22 | | threads | 1 | Number of threads |
23 | | blocking-trheads | 1 | Number of blocking threads |
24 | | runtime-mode | st | Runtime implementation (possible values: st,mt) |
25 | | interface | rsgi | Server interface (possible values: rsgi,asgi) |
26 | | http | auto | HTTP protocol version (possible values: auto,1,2) |
27 | | http-read-timeout | 10000 | HTTP read timeout (in milliseconds) |
28 | | ws/no-ws | ws | Enable/disable websockets support |
29 | | loop | auto | Loop implementation (possible values: auto,asyncio,rloop,uvloop) |
30 | | log-level | info | Logging level (possible values: debug,info,warning,error,critical) |
31 | | backlog | 2048 | Maximum connection queue |
32 | | backpressure | | Maximum number of requests to process concurrently |
33 | | ssl-certfile | | Path to SSL certificate file |
34 | | ssl-keyfile | | Path to SSL key file |
35 |
36 | Other ASGI servers
37 | ------------------
38 |
39 | *Changed in version 2.7*
40 |
41 | Since an Emmett application object is also an [ASGI](https://asgi.readthedocs.io/en/latest/) application, you can serve your project with any [ASGI compliant server](https://asgi.readthedocs.io/en/latest/implementations.html#servers).
42 |
43 | To serve your project with such servers, just refer to the specific server documentation an point it to your application object.
44 |
45 | Docker
46 | ------
47 |
48 | Even if Docker is not properly a deployment option, we think giving an example of a `Dockerfile` for an Emmett application is proficient for deployment solutions using container orchestrators, such as Kubernetes or Mesos.
49 |
50 | In order to keep the image lighter, we suggest to use the *slim* Python image as a source:
51 |
52 | ```Dockerfile
53 | FROM python:3.9-slim
54 |
55 | RUN mkdir -p /usr/src/deps
56 | COPY requirements.txt /usr/src/deps
57 |
58 | WORKDIR /usr/src/deps
59 | RUN pip install --no-cache-dir -r /usr/src/deps/requirements.txt
60 |
61 | COPY ./ /app
62 | WORKDIR /app
63 |
64 | EXPOSE 8000
65 |
66 | CMD [ "emmett", "serve" ]
67 | ```
68 |
--------------------------------------------------------------------------------
/docs/foreword.md:
--------------------------------------------------------------------------------
1 |
2 | Foreword
3 | ========
4 |
5 | When I started writing web applications, the first "big decision" I faced was the choice of the programming language.
6 |
7 | As a programmer, it's not a big deal to work with different languages, and switching from a language to another, shouldn't be too hard, apart from learning a bit of syntax; at the same time everybody needs pick a language to use as a *daily habit*. We are humans, after all.
8 |
9 | I wanted a dynamic language to write my applications, a language with an easier syntax than PHP, and more structured than JavaScript. I wanted something that allowed me to write that *little magic* every developer does behind the scenes in a handy way. And I finally chose Python. Ruby was on the list (it seemed quite popular for web development at the time), but I've found its syntax, in some situation, less immediate; moreover, Python is somewhat more *solid* than Ruby – maybe even too much solid. I think even Python would be advantaged by a more dynamic community: just think about Python 3's adoption status (Ed.).
10 |
11 | I really enjoyed writing code in Python, and after gaining some confidence, I faced the second "big decision": which framework to use to write my applications Looking at the Python scene, I (obviously) started looking at *django*, the most famous one, but after a while I found I didn't like it. It wasn't as user friendly as I had hoped. Then I found *web2py*, and I loved it from the first line of the documentation book: it was simple, full of features, and learning it was much quicker than *django*.
12 |
13 | Nevertheless, after some years of using *web2py*, inspecting deeply the code and logic, and contributing it, I started having a feeling. A need grew in my mind while writing applications, to write things differently. I found myself thinking "Why should I write this stuff in *this* way? It's not cool or handy at all," and I had to face the problem that doing what I wanted would involve completely re-designing the whole framework.
14 |
15 | With this nagging feeling in my mind, I started looking around and found that a lot of the syntax and logic in *Flask* were the answer to what I was looking for.
16 | Unfortunately, at the same time, *Flask* had a lacked many of the features I was used to having out of the box with *web2py*, and not even using extensions would have been enough to cover it all.
17 |
18 | I naturally came to the conclusion that I was at *that point* of my coding life where I needed a "custom-designed tool".
19 |
20 | Why not?
21 | --------
22 |
23 | > – Hey dude, what are you doing?
24 | > – *writing a new python web framework..*
25 | > – Whoa! Why would you do that?
26 | > – *...why not?*
27 |
28 | That was my answer when a friend of mine asked me the reasons behind my intention of building a new framework. It was a legitimate question: there are many frameworks on the scene. Is it really a good move to build a new tool rather than picking one of the available ones?
29 |
30 | I'd like to reply to this doubt with a definition of *tool* I really love:
31 |
32 | > **tool:** *something* intended to make a task easier.
33 |
34 | So a framework, which is a tool, has to let you write your application **easier** than without it. Now, I've found many frameworks – and I'm sure you can easily find them, too – where you have to deal with learning *a lot* of "how to do that" with the framework itself instead of focusing on the application.
35 |
36 | This is the first principle I've based *Emmett* on: **it should be easy to use and learn, so that you can focus on *your* product.**
37 |
38 | Another key principle of *Emmett* is the *preservation of your control* over the flow. What do I mean? There are several frameworks that do too much *magic* behind the scenes. I know that may sound weird because I've just talked about simplicity, but, if you think about it, you will find that a framework that is simple to use is not necessarily one which hides a lot of his flow.
39 |
40 | As developers, we have to admit that when we use frameworks or libraries for our project, many times it is hard to do something out of the ready-made scheme. I can think about several frameworks – even the famous *Ruby on Rails* – that, from time to time, force you to use a lot of formalism even when it's not really necessary. You find yourself writing code while following useless rules you don't like.
41 |
42 | In other words: I like magic too, but **isn't cooler when you actually *control* the magic?**
43 |
44 | With these principles in mind, I've tried to build a complete tool, something intended to make your task easier, with a rich set of features in the box.
45 | The result of my recipe is a framework which has an easy syntax, similar to *Flask*, but which also includes some of the lovable features of *web2py*.
46 |
47 | I hope you like it.
48 |
49 | Acknowledgments
50 | ---------------
51 |
52 | I would like to thank:
53 |
54 | * All the **Emmett contributors**
55 | * **Guido Van Rossum**, for the Python language
56 | * **Massimo Di Pierro** and **web2py's developers**, for what I learned from them and for their framework on which I based Emmett
57 | * **Armin Ronacher**, who really inspired me with the Flask framework
58 | * **Marco Stagni**, **Michael Genesini** and **Filippo Zanella** for their advices and continuous support
59 |
--------------------------------------------------------------------------------
/docs/html.md:
--------------------------------------------------------------------------------
1 | HTML without templates
2 | ======================
3 |
4 | As we saw in the [templates chapter](./templates), Emmett comes with a template engine out of the box, which you can use to render HTML.
5 |
6 | Under specific circumstances though, it might be convenient generating HTML directly in your route code, using the Python language. To support these scenarios, Emmett provides few helpers under the `html` module. Let's see them in details.
7 |
8 | The `tag` helper
9 | ----------------
10 |
11 | The `tag` object is the main interface provided by Emmett to produce HTML contents from Python code. It dinamically produces HTML elements based on its attributes, so you can produce even custom elements:
12 |
13 | ```python
14 | from emmett.html import tag
15 |
16 | # an empty
17 | p = tag.p()
18 | # a custom element
19 | card = tag.card()
20 | # a custom element
21 | list_item = tag["list-item"]()
22 | ```
23 |
24 | Every element produced by the `tag` helper accepts both nested contents and attributes, with the caveat HTML attributes needs to start with `_`:
25 |
26 | ```python
27 | #
Hello world
28 | p = tag.p("Hello world")
29 | #
bar
30 | div = tag.div(tag.p("bar"), _class="foo")
31 | ```
32 |
33 | > **Note:** the reasons behind the underscore notation for HTML attributes are mainly:
34 | > - to avoid issues with Python reserved words (eg: `class`)
35 | > - to keep the ability to set custom attributes on the HTML objects in Python code but prevent those attributes to be rendered
36 |
37 | Mind that the `tag` helper already takes care of *self-closing* elements and escaping contents, so you don't have to worry about those.
38 |
39 | > – That's cool dude, but what if I need to set several attributes with the same prefix?
40 | > – *Like with HTMX? Sure, just use a dictionary*
41 |
42 | ```python
43 | #
44 | btn = tag.button(
45 | "Click me",
46 | _hx={
47 | "post": url("clicked"),
48 | "swap": "outerHTML"
49 | }
50 | )
51 | ```
52 |
53 | The `cat` helper
54 | ----------------
55 |
56 | Sometimes you may need to stack together HTML elements without a parent. For such cases, the `cat` helper can be used:
57 |
58 | ```python
59 | from emmett.html import cat, tag
60 |
61 | #
hello
world
62 | multi_p = cat(tag.p("hello"), tag.p("world"))
63 | ```
64 |
65 | Building deep stacks
66 | --------------------
67 |
68 | All the elements produced with the `tag` helper supports `with` statements, so you can easily manage even complicated stacks. For instance the following code:
69 |
70 | ```python
71 | root = tag.div(_class="root")
72 | with root:
73 | with tag.div(_class="lv1"):
74 | with tag.div(_class="lvl2"):
75 | tag.p("foo")
76 | tag.p("bar")
77 |
78 | str(root)
79 | ```
80 |
81 | will produce the following HTML:
82 |
83 | ```html
84 |
85 |
86 |
87 |
foo
88 |
bar
89 |
90 |
91 |
92 | ```
93 |
94 | > **Note:** when compared to templates, HTML generation from Python will be noticeably slower. For cases in which you want to render long and almost static HTML contents, using templates is preferable.
95 |
--------------------------------------------------------------------------------
/docs/installation.md:
--------------------------------------------------------------------------------
1 |
2 | Installation
3 | ============
4 |
5 | So, how do you get Emmett on your computer quickly? There are many ways you could do that, but the most kick-ass method is virtualenv, so let’s have a look at that first.
6 |
7 | You will need Python version 3.9 or higher in order to get Emmett working.
8 |
9 | virtualenv
10 | ----------
11 |
12 | Virtualenv is probably what you want to use during development, and if you have shell access to your production machines, you’ll probably want to use it there, too.
13 |
14 | What problem does virtualenv solve? If you use Python a bit, you'll probably want to use it for other projects besides Emmett-based web applications. However, the more projects you have, the more likely it is that you will be working with different versions of Python itself, or at least different versions of Python libraries. Let’s face it: quite often, libraries break backwards compatibility, and it’s unlikely that any serious application will have zero dependencies. So what do you do if two or more of your projects have conflicting dependencies?
15 |
16 | Virtualenv to the rescue! Virtualenv enables multiple side-by-side installations of Python, one for each project. It doesn’t actually install separate copies of Python, but it does provide a clever way to keep different project environments isolated.
17 | Let’s see how virtualenv works.
18 |
19 | #### virtualenv on Python 3
20 |
21 | You can just initialize your environment in the *.venv* folder using:
22 |
23 | ```bash
24 | $ mkdir -p myproject
25 | $ cd myproject
26 | $ python -m venv .venv
27 | ```
28 |
29 | ### Installing Emmett on virtualenv
30 |
31 | Now, whenever you want to work on a project, you only have to activate the corresponding environment. On OS X and Linux, you can do the following:
32 |
33 | ```bash
34 | $ source .venv/bin/activate
35 | ```
36 |
37 | You should now be using your virtualenv (notice how the prompt of your shell has changed to show the active environment).
38 |
39 | Now you can just enter the following command to get Emmett activated in your virtualenv:
40 |
41 | ```bash
42 | $ pip install emmett
43 | ```
44 |
45 | And now you are good to go.
46 |
47 | You can read more about virtualenv on [its documentation website](https://docs.python.org/3/library/venv.html).
48 |
--------------------------------------------------------------------------------
/docs/languages.md:
--------------------------------------------------------------------------------
1 | Languages and internationalization
2 | ==================================
3 |
4 | Emmett provides *Severus* as its integrated internationalization engine for managing multiple languages in your application. How does it work?
5 |
6 | ```python
7 | from emmett import App, T
8 | app = App(__name__)
9 |
10 | @app.route("/")
11 | async def index():
12 | hello = T('Hello, my dear!')
13 | return dict(hello=hello)
14 | ```
15 |
16 | As you can see, Emmett expose a language translator with the `T` object.
17 | It tells Emmett to translate the string depending on clients locale, and is also available in the templating environment.
18 |
19 | So what you should do with the other languages' text? You can just leave it in *json* or *yaml* files within a *languages* directory. Each file should be named after the international symbol for the language. For example, an Italian translation should be stored as *languages/it.json*:
20 |
21 | ```json
22 | {
23 | "Hello, my dear!": "Ciao, mio caro!"
24 | }
25 | ```
26 |
27 | and the hello message will be translated when the user request the Italian language.
28 |
29 | > – awesome. Though, how does Emmett decides which language should be loaded for
30 | a specific client?
31 | > – *actually, you can choose that*
32 |
33 | Emmett has two different ways to handle languages in your application:
34 |
35 | * using clients' HTTP headers
36 | * using URLs
37 |
38 | Let's see the differences between the two systems.
39 |
40 | Translation using HTTP headers
41 | ------------------------------
42 |
43 | Let's say your application has English for its default language and you want your application to also be available in Italian, as the above example.
44 | With default settings, the user's requested language is determined by the "Accept-Language" field in the HTTP header. This means that if *User 1* has their browser set to accept Italian, he will see the Italian version when visiting 'http://127.0.0.1:8000/'. Meanwhile, if *User 2* has their browser set for any other language, they will see the english one.
45 |
46 | Using this translation technique is quite easy. The available languages are
47 | defined automatically, based on the translation files you have inside *languages*
48 | folder of your application.
49 |
50 | ```
51 | /myapp
52 | /languages
53 | it.json
54 | ```
55 |
56 | will make your application available in Italian in addition to the English written in your templates and exposed functions.
57 |
58 | Simply add more translation programs to increase your multi-language support:
59 |
60 | ```
61 | /myapp
62 | /languages
63 | it.json
64 | es.yaml
65 | ```
66 |
67 | will make your application available in English, Italian and Spanish.
68 |
69 | You can change the default language of your application with the following line in the file where you wrote your application's exposed functions:
70 |
71 | ```python
72 | app.language_default = 'it'
73 | ```
74 |
75 | Translation using routing
76 | -------------------------
77 |
78 | There are many scenarios where you want your application to use different URLs to separate contents based on the language.
79 |
80 | Let's say again you have your application with English as the default language and you provide a supplementary Italian version; to achieve the routing translation behavior under Emmett, you should write:
81 |
82 | ```python
83 | app.languages = ['en', 'it']
84 | app.language_default = 'en'
85 | app.language_force_on_url = True
86 | ```
87 | and Emmett will automatically add the support for language on your routing rules to the follow:
88 |
89 | | requested URL | behaviour |
90 | | --- | --- |
91 | | /anexampleurl | shows up the contents with the default language |
92 | | /it/anexampleurl | shows up the contents with the italian language |
93 |
94 | As you can see, the *routing* way requires that you to explicitly tell to Emmett which languages should be available into your application, so it can build the routing tables.
95 |
--------------------------------------------------------------------------------
/docs/mailer.md:
--------------------------------------------------------------------------------
1 | Sending mails
2 | =============
3 |
4 | Sooner or later you will need to send mails to your users from your application. Emmett provides a simple interface to set up SMTP with your application and to send messages to your users.
5 |
6 | Let's start configuring a simple mailer within our application:
7 |
8 | ```python
9 | from emmett import App
10 | from emmett.tools import Mailer
11 |
12 | app = App(__name__)
13 | app.config.mailer.sender = "nina@massivedynamic.com"
14 |
15 | mailer = Mailer(app)
16 | ```
17 |
18 | With just these lines the mailer is ready to send messages, using the local machine as the SMTP server and sending messages from the address *nina@massivedynamic.com*.
19 |
20 | The mailer also accepts additional configuration parameters, here is the complete list:
21 |
22 | | parameter | default value | description |
23 | | --- | --- | --- |
24 | | sender | `None` | the address to use for the *From* value |
25 | | server | 127.0.0.1 | the SMTP host to use |
26 | | port | 25 | the SMTP port to use |
27 | | username | `None` | the username to authenticate with (if needed) |
28 | | password | `None` | the password to authenticate with (if needed) |
29 | | use\_tls | `False` | decide if TLS should be used |
30 | | use\_ssl | `False` | decide if SSL should be used |
31 |
32 | Now, let's see how to send messages.
33 |
34 | Sending messages
35 | ----------------
36 |
37 | We can create a simple message using the `mail` method of our mailer:
38 |
39 | ```python
40 | message = mailer.mail(
41 | subject="Hello",
42 | body="A very important message",
43 | recipients=["walter@massivedynamic.com"])
44 | ```
45 |
46 | and add another recipient later:
47 |
48 | ```python
49 | message.add_recipient("william@massivedynamics.com")
50 | ```
51 |
52 | and when we are ready, we can just send it:
53 |
54 | ```python
55 | message.send()
56 | ```
57 |
58 | We can also create a message and send it directly:
59 |
60 | ```python
61 | mailer.send_mail(
62 | subject="Hello",
63 | body="A very important message",
64 | recipients=["walter@massivedynamic.com"])
65 | ```
66 |
67 | or set an html content for the message:
68 |
69 | ```python
70 | message.html = "Testing"
71 | ```
72 |
73 | Attachments
74 | -----------
75 |
76 | Once you created a message, adding attachments is quite easy:
77 |
78 | ```python
79 | msg = mailer.mail(subject="See this")
80 |
81 | with open("image.png") as fp:
82 | msg.attach(filename="image.png", data=fp.read())
83 |
84 | msg.recipients = ["walter@massivedynamic.com"]
85 | msg.send()
86 | ```
87 |
88 | > **Note:** The default encoding used by the mailer is utf-8.
89 |
90 | Tests and suppression
91 | ---------------------
92 |
93 | When you are testing your application, or if you are in a development environment, it’s useful to be able to suppress email sending. Still, you may want to test some message was generated in your code, and you want to catch it.
94 |
95 | The mailer provide a `store_mails` method for this, so you can just write down:
96 |
97 | ```python
98 | with mailer.store_mails() as outbox:
99 | mailer.send_mail(
100 | subject='testing', body='test', recipients=["foo@bar.com"])
101 | assert len(outbox) == 1
102 | assert outbox[0].subject == "testing"
103 | ```
104 |
105 | and the mailer just avoid the *real sending* of the message.
106 |
107 | If you want to totally disable email sending, you can use the `suppress` parameter in the configuration:
108 |
109 | ```python
110 | app.config.mailer.suppress = True
111 | ```
112 |
113 | This will completely disable message sending.
114 |
--------------------------------------------------------------------------------
/docs/orm.md:
--------------------------------------------------------------------------------
1 | Using databases
2 | ===============
3 |
4 | > – OK, what if I need to use a database in my application?
5 | > – *you can use the included ORM*
6 |
7 | Emmett comes with an integrated ORM based on [pyDAL](https://github.com/web2py/pydal), which gives you the ability to use a database in your application writing simple Python code without worrying about queries and specific syntax of the database engine you want to use.
8 |
9 | Thanks to this database layer, you can write the same code and use the same syntax independently of which of the available adapters you want to use during development or when you're deploying your app to the world.
10 |
11 | This is the list of the supported database engines, where we included the name used by Emmett for the connection configuration and the appropriate driver you need to install, separately from Emmett (just use pip):
12 |
13 | | Supported DBMS | adapter name | python driver |
14 | | --- | --- | --- |
15 | | SQLite | sqlite | |
16 | | PostgreSQL | postgres | psycopg2, pyscopg 3 (experimental), pg8000, zxjdbc |
17 | | MySQL | mysql | pymysql, mysqldb |
18 | | MSSQL | mssql | pyodbc |
19 | | MongoDB | mongo | pymongo |
20 |
21 | The next database engines are included in Emmett but are targeted with *experimental* support, as they're not officially supported by the Emmett development:
22 |
23 | | Experimental support | adapter name | python driver(s) |
24 | | --- | --- | --- |
25 | | CouchDB | couchdb | couchdb |
26 | | Oracle | oracle | cxoracle |
27 | | FireBird | firebird | kinterbasdb, fdb, pyodbc |
28 | | DB2 | db2 | pyodbc |
29 | | Informix | informix | informixdb |
30 | | Ingres | ingres | ingresdbi |
31 | | Cubrid | cubrid | cubridb |
32 | | Sybase | sybase | Sybase |
33 | | Teradata | teradata | pyodbc |
34 | | SAPDB | sapdb | sapdb |
35 |
36 | > **Note:**
37 | > This list may change, and depends on the engine support of pyDAL. For any
38 | further information, please check out the [project page](https://github.com/web2py/pydal).
39 |
40 | So, how do you use Emmett's ORM? Let's see it with an example:
41 |
42 | ```python
43 | from emmett import App
44 | from emmett.orm import Database, Model, Field
45 |
46 | app = App(__name__)
47 | app.config.db.uri = "sqlite://storage.sqlite"
48 |
49 | class Post(Model):
50 | author = Field()
51 | title = Field()
52 | body = Field.text()
53 |
54 | db = Database(app)
55 | db.define_models(Post)
56 |
57 | app.pipeline = [db.pipe]
58 |
59 | @app.route('/posts/')
60 | def post_by(author):
61 | posts = db(Post.author == author).select()
62 | return dict(posts=posts)
63 | ```
64 |
65 | The above code is quite simple: the `post_by()` function lists posts from a
66 | specific author. Let's retrace what we done in those simple lines:
67 |
68 | * we added an *sqlite* database to our application, stored on file *storage.sqlite*
69 | * we defined the *Post* model and its properties, which will create a *posts* table
70 | * we registered the database pipe to our application's pipeline so that it will be available during requests
71 | * we did a select on the *posts* table querying the *author* column
72 |
73 | As you noticed, the fields defined for the table are available for queries as
74 | attributes, and calling *db* with a query argument provides you a set on
75 | which you can do operations like the `select()`.
76 |
77 | In the next chapters, we will inspect how to define models, all the available options,
78 | and how to use Emmett's ORM to perform operations on the database.
79 |
--------------------------------------------------------------------------------
/docs/patterns.md:
--------------------------------------------------------------------------------
1 | Patterns for Emmett
2 | ===================
3 |
4 | Emmett is crafted to fit the needs of many applications, from the smallest to the
5 | largest ones. Due to this, your application can be built up from a single Python
6 | file and scale to a better organized structure.
7 |
8 | In this section, we will cover some good *patterns* you may follow when your
9 | application starts becoming large, or when you just need to organize your code
10 | better.
11 |
12 | Package pattern
13 | ---------------
14 |
15 | The package pattern will make your application a Python package instead of a module.
16 | For instance, let's assume your original application is structured like that:
17 |
18 | ```
19 | /myapp
20 | myapp.py
21 | /static
22 | style.css
23 | /templates
24 | layout.html
25 | index.html
26 | login.html
27 | ...
28 | ```
29 |
30 | To convert it to a package application, you should create another folder inside
31 | your original *myapp* one, and rename *myapp.py* to *\__init__.py*, ending up
32 | with something like this:
33 |
34 | ```
35 | /myapp
36 | /myapp
37 | __init__.py
38 | /static
39 | style.css
40 | /templates
41 | layout.html
42 | index.html
43 | login.html
44 | ...
45 | ```
46 |
47 | > – OK, dude. But what did we gain by doing this?
48 | > – *well, now we can organize the code in multiple modules*
49 |
50 | With this new structure, we can create a new *views.py* file inside the package
51 | and we can move the routed functions to it. For example, your *\__init__.py* file
52 | can look like this:
53 |
54 | ```python
55 | from emmett import App
56 |
57 | app = App(__name__)
58 |
59 | from . import views
60 | ```
61 |
62 | and your *views.py* could look like:
63 |
64 | ```python
65 | from . import app
66 |
67 | @app.route("/")
68 | async def index():
69 | # some code
70 | ```
71 |
72 | Your final structure would be like this:
73 |
74 | ```
75 | /myapp
76 | /myapp
77 | __init__.py
78 | views.py
79 | /static
80 | style.css
81 | /templates
82 | layout.html
83 | index.html
84 | login.html
85 | ...
86 | ```
87 |
88 | > – That's nice, but how can I run my application now?
89 |
90 | You can use the Emmett command inside the original directory of your application:
91 |
92 | ```bash
93 | $ emmett -a myapp develop
94 | ```
95 |
96 | > **A note regarding circular imports:**
97 | > Every Python developer hates them, and yet we just added some of them: *views.py* depends on *\_\_init\_\_.py* while *\_\_init\_\_.py* imports *views.py*. In general, this is a bad idea, but it is actually fine here because we are not actually using the views in *\_\_init\_\_.py*. We are ensuring that the module is imported to expose the functions; also, we are doing that at the bottom of the file.
98 |
99 | MVC pattern
100 | -----------
101 | The **MVC** (Model-View-Controller) pattern, used widely in web applications, is well structured and becomes handy when you have big applications. Emmett does not provide controllers, but you can implement an MVC pattern using application modules. An MVC structure for an Emmett application can look something like this:
102 |
103 | ```
104 | /myapp
105 | __init__.py
106 | /controllers
107 | __init__.py
108 | main.py
109 | api.py
110 | /models
111 | __init__.py
112 | user.py
113 | article.py
114 | /templates
115 | layout.html
116 | index.html
117 | login.html
118 | ...
119 | ```
120 |
121 | As you can see, it's an extension of the *package pattern*, where we added the
122 | two sub-packages *controllers* and *models*, each with an empty *\_\_init\_\_.py* file.
123 |
124 | With this structure, your application's *\_\_init\_\_.py* would look like this:
125 |
126 | ```python
127 | from emmett import App
128 | from emmett.orm import Database
129 |
130 | app = App(__name__)
131 | app.config.url_default_namespace = "main"
132 |
133 | db = Database()
134 |
135 | from .models.user import User
136 | from .models.article import Post
137 | db.define_models(User, Post)
138 |
139 | from .controllers import main, api
140 | ```
141 |
142 | We told Emmett to use the *main.py* controller as default for urls, so we can just
143 | call `url('index')` instead of `url('main.function')` in our application.
144 |
145 | The main controller can look like this:
146 |
147 | ```python
148 | from .. import app
149 |
150 | @app.route("/")
151 | async def index():
152 | # code
153 | ```
154 |
155 | and the *api.py* controller can look like this:
156 |
157 | ```python
158 | from .. import app
159 |
160 | api = app.module(__name__, 'api', url_prefix='api')
161 |
162 | @api.route()
163 | async def a():
164 | # code
165 | ```
166 |
--------------------------------------------------------------------------------
/docs/services.md:
--------------------------------------------------------------------------------
1 | Services
2 | ========
3 |
4 | Quite often, you will need to render the output of your application using a
5 | protocol other than HTML; for example, JSON or XML.
6 |
7 | Emmett can help you expose those services with the `service` decorator:
8 |
9 | ```python
10 | from emmett import App
11 | from emmett.tools import service
12 |
13 | app = App(__name__)
14 |
15 | @app.route("/json")
16 | @service.json
17 | async def f():
18 | # your code
19 | ```
20 | The output will be automatically converted using the required service
21 | (JSON in this example).
22 |
23 | > – awesome. But, what if I need to expose several function with a service?
24 | Should I decorate every function?
25 | > – *you can use the provided pipe, dude*
26 |
27 | Emmett also provides a `ServicePipe` object so you can create an application module with all the functions you want to expose with a specific service and add the pipe to the module:
28 |
29 | ```python
30 | from emmett.tools import ServicePipe
31 | from myapp import app
32 |
33 | api = app.module(__name__, 'api')
34 | api.pipeline = [ServicePipe('json')]
35 |
36 | @api.route()
37 | async def a():
38 | # code
39 |
40 | @api.route()
41 | async def b():
42 | # code
43 | ```
44 |
45 | So, which are the available services? Let's see them.
46 |
47 | JSON and XML
48 | ------------
49 |
50 | Providing a JSON service with Emmett is quite easy:
51 |
52 | ```python
53 | @app.route("/json")
54 | @service.json
55 | async def f():
56 | l = [1, 2, {'foo': 'bar'}]
57 | return dict(status="OK", data=l)
58 | ```
59 |
60 | The output will be a JSON object with the converted content of your python
61 | dictionary:
62 |
63 | ```json
64 | {
65 | "status": "OK",
66 | "data": [
67 | 1,
68 | 2,
69 | {
70 | "foo": "bar",
71 | }
72 | ]
73 | }
74 | ```
75 |
76 | To provide an XML service, just decorate your function using the next line
77 | instead:
78 |
79 | ```python
80 | @service.xml
81 | ```
82 |
83 | Obviously, the syntax for using `ServicePipe` is the same as in the
84 | first example:
85 |
86 | ```python
87 | # providing a JSON service pipe
88 | ServicePipe('json')
89 |
90 | # providing an XML service pipe
91 | ServicePipe('xml')
92 | ```
93 |
94 | Multiple services
95 | -----------------
96 |
97 | Sometimes you may want to expose several services for a single endpoint, for example a list of items both in JSON and XML format.
98 |
99 | You can easily achieve this decorating your route multiple times, using different pipelines:
100 |
101 | ```python
102 | from emmett.tools import ServicePipe
103 |
104 | @app.route('/elements.json', pipeline=[ServicePipe('json')])
105 | @app.route('/elements.xml', pipeline=[ServicePipe('xml')])
106 | async def elements():
107 | return [{"foo": "bar"}, {"bar": "foo"}]
108 | ```
109 |
110 | With this notation, you can serve different services using the same exposed method.
111 |
--------------------------------------------------------------------------------
/docs/sessions.md:
--------------------------------------------------------------------------------
1 | Sessions
2 | ========
3 |
4 | An essential feature for a web application is the ability to store specific informations about the client from a request to the next one. Accordingly to this need, Emmett provides another object beside the `request` and the `response` ones called `session`.
5 |
6 | ```python
7 | from emmett import session
8 |
9 | @app.route("/counter")
10 | async def count():
11 | session.counter = (session.counter or 0) + 1
12 | return "This is your %d visit" % session.counter
13 | ```
14 |
15 | The above code is quite simple: the app increments the counter every time the user visit the page and return this number to the user.
16 | Basically, you can use `session` object to store and retrieve data, but before you can do that, you should add a *SessionManager* to your application pipeline. These managers allows you to store sessions' data on different storage systems, depending on your needs. Let's see them in detail.
17 |
18 | Storing sessions in cookies
19 | ---------------------------
20 |
21 | *Changed in version 2.5*
22 |
23 | You can store session contents directly in the cookies of the client using the Emmett's `SessionManager.cookies` pipe:
24 |
25 | ```python
26 | from emmett import App, session
27 | from emmett.sessions import SessionManager
28 |
29 | app = App(__name__)
30 | app.pipeline = [SessionManager.cookies('myverysecretkey')]
31 |
32 | @app.route("/counter")
33 | # previous code
34 | ```
35 |
36 | As you can see, `SessionManager.cookies` needs a secret key to crypt the sessions' data and keep them secure – you should choose a good key – but also accepts more parameters:
37 |
38 | | parameter | default value | description |
39 | | --- | --- | --- |
40 | | expire | 3600 | the duration in seconds after which the session will expire |
41 | | secure | `False` | tells the manager to allow *https* sessions only |
42 | | samesite | Lax | set `SameSite` option for the cookie |
43 | | domain | | allows to set a specific domain for the cookie |
44 | | cookie\_name | | allows to set a specific name for the cookie |
45 | | cookie\_data | | allows to pass additional cookie data to the manager |
46 | | compression\_level | 0 | allows to set the compression level for the data stored (0 means disabled) |
47 |
48 | Storing sessions on filesystem
49 | ------------------------------
50 |
51 | *Changed in version 2.1*
52 |
53 | You can store session contents on the server's filesystem using the Emmett's `SessionManager.files` pipe:
54 |
55 | ```python
56 | from emmett import App, session
57 | from emmett.sessions import SessionManager
58 |
59 | app = App(__name__)
60 | app.pipeline = [SessionManager.files()]
61 |
62 | @app.route("/counter")
63 | # previous code
64 | ```
65 |
66 | As you can see, `SessionManager.files` doesn't require specific parameters, but it accepts these optional ones:
67 |
68 | | parameter | default value | description |
69 | | --- | --- | --- |
70 | | expire | 3600 | the duration in seconds after which the session will expire |
71 | | secure | `False` | tells the manager to allow sessions only on *https* protocol |
72 | | samesite | Lax | set `SameSite` option for the cookie |
73 | | domain | | allows to set a specific domain for the cookie |
74 | | cookie\_name | | allows to set a specific name for the cookie |
75 | | cookie\_data | | allows to pass additional cookie data to the manager |
76 | | filename_template | `'emt_%s.sess'` | allows you to set a specific format for the files created to store the data |
77 |
78 | Storing sessions using redis
79 | ----------------------------
80 |
81 | *Changed in version 2.1*
82 |
83 | You can store session contents using *redis* – you obviously need the redis package for python – with the Emmett's `SessionManager.redis` pipe:
84 |
85 | ```python
86 | from redis import Redis
87 | from emmett import App, session
88 | from emmett.sessions import SessionManager
89 |
90 | app = App(__name__)
91 | red = Redis(host='127.0.0.1', port=6379)
92 | app.pipeline = [SessionManager.redis(red)]
93 |
94 | @app.route("/counter")
95 | # previous code
96 | ```
97 |
98 | As you can see `SessionManager.redis` needs a redis connection as first parameter, but as for the cookie manager, it also accepts more parameters:
99 |
100 | | parameter | default | description |
101 | | --- | --- | --- |
102 | | prefix | `'emtsess:'` | the prefix for the redis keys (default set to |
103 | | expire | 3600 | the duration in seconds after which the session will expire |
104 | | secure | `False` | tells the manager to allow sessions only on *https* protocol |
105 | | samesite | Lax | set `SameSite` option for the cookie |
106 | | domain | | allows to set a specific domain for the cookie |
107 | | cookie\_name | | allows to set a specific name for the cookie |
108 | | cookie\_data | | allows to pass additional cookie data to the manager |
109 |
110 | The `expire` parameter tells redis when to auto-delete the unused session: every time the session is updated, the expiration time is reset to the one specified.
111 |
--------------------------------------------------------------------------------
/docs/tree.yml:
--------------------------------------------------------------------------------
1 | --- # tree
2 | - foreword
3 | - installation
4 | - quickstart
5 | - tutorial
6 | - app_and_modules
7 | - routing
8 | - templates
9 | - html
10 | - request
11 | - response
12 | - websocket
13 | - pipeline
14 | - sessions
15 | - languages
16 | - orm:
17 | - connecting
18 | - models
19 | - operations
20 | - scopes
21 | - relations
22 | - virtuals
23 | - callbacks
24 | - migrations
25 | - advanced
26 | - validations
27 | - forms
28 | - auth
29 | - mailer
30 | - caching
31 | - services
32 | - testing
33 | - debug_and_logging
34 | - extensions
35 | - cli
36 | - patterns
37 | - deployment
38 | - upgrading
39 |
--------------------------------------------------------------------------------
/docs/websocket.md:
--------------------------------------------------------------------------------
1 | Handling websockets
2 | ===================
3 |
4 | *New in version 2.0*
5 |
6 | In the same way we saw for [requests](./request), Emmett also provides facilities to help you dealing with websockets in your application.
7 |
8 | The websocket object
9 | --------------------
10 |
11 | When a websocket connection comes from a client, Emmett binds useful informations about it within the `websocket` object, which can be accessed just with an import:
12 |
13 | ```python
14 | from emmett import websocket
15 | ```
16 |
17 | It contains useful information about the current processing socket, in particular:
18 |
19 | | attribute | description |
20 | | --- | --- |
21 | | scheme | could be *ws* or *wss* |
22 | | path | full path of the request |
23 | | host | hostname of the request |
24 | | headers | the headers of the request |
25 | | cookies | the cookies passed with the request |
26 |
27 | Now, let's see how to deal with request variables.
28 |
29 | ### Request variables
30 |
31 | Emmett's `websocket` object also shares the same attributes of `request` when available:
32 |
33 | | attribute | description |
34 | | --- | --- |
35 | | query_params | contains the URL query parameters |
36 |
37 | and also in websockets, this attribute is an `sdict` object so when the URL doesn't contain the query parameter you're trying to look at, this will be `None`, so it's completely safe to call it. It won't raise any exception.
38 |
39 |
40 | Sending and receiving messages
41 | ------------------------------
42 |
43 | The main difference between request routes and websocket ones is the communication flow. In fact, while in standard routes you just write a return value, with sockets you can receive and send multiple messages within the same connection.
44 |
45 | This is why the `websocket` object in Emmett also has three awaitable methods for this purpose:
46 |
47 | - accept
48 | - receive
49 | - send
50 |
51 | While the `accept` method is implicitly called by the former ones, and is exposed in case you want to specify a specific flow for websockets acceptance, the `receive` and `send` method will be used by Emmett to deal with communications.
52 |
53 | Giving an example, a super simple echo websocket in Emmett will look like this:
54 |
55 | ```python
56 | from emmett import websocket
57 |
58 | @app.websocket()
59 | async def echo():
60 | while True:
61 | message = await websocket.receive()
62 | await websocket.send(message)
63 | ```
64 |
65 | Mind that, since a websocket route essentially is a loop, when your code returns Emmett will close the connection.
66 |
--------------------------------------------------------------------------------
/emmett/__init__.py:
--------------------------------------------------------------------------------
1 | from . import _internal
2 | from .app import App, AppModule
3 | from .cache import Cache
4 | from .ctx import current
5 | from .datastructures import sdict
6 | from .forms import Form
7 | from .helpers import abort, stream_file
8 | from .html import asis
9 | from .http import redirect
10 | from .locals import T, now, request, response, session, websocket
11 | from .orm import Field
12 | from .pipeline import Injector, Pipe
13 | from .routing.urls import url
14 |
--------------------------------------------------------------------------------
/emmett/__main__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | emmett.__main__
4 | ----------------
5 |
6 | Alias for Emmett CLI.
7 |
8 | :copyright: 2014 Giovanni Barillari
9 | :license: BSD-3-Clause
10 | """
11 |
12 | from emmett.cli import main
13 |
14 |
15 | main(as_module=True)
16 |
--------------------------------------------------------------------------------
/emmett/__version__.py:
--------------------------------------------------------------------------------
1 | __version__ = "2.7.0"
2 |
--------------------------------------------------------------------------------
/emmett/_internal.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | emmett._internal
4 | ----------------
5 |
6 | Provides internally used helpers and objects.
7 |
8 | :copyright: 2014 Giovanni Barillari
9 |
10 | Several parts of this code comes from Flask and Werkzeug.
11 | :copyright: (c) 2014 by Armin Ronacher.
12 |
13 | :license: BSD-3-Clause
14 | """
15 |
16 | from __future__ import annotations
17 |
18 | import datetime
19 |
20 | import pendulum
21 |
22 |
23 | #: monkey patches
24 | def _pendulum_to_datetime(obj):
25 | return datetime.datetime(
26 | obj.year, obj.month, obj.day, obj.hour, obj.minute, obj.second, obj.microsecond, tzinfo=obj.tzinfo
27 | )
28 |
29 |
30 | def _pendulum_to_naive_datetime(obj):
31 | obj = obj.in_timezone("UTC")
32 | return datetime.datetime(obj.year, obj.month, obj.day, obj.hour, obj.minute, obj.second, obj.microsecond)
33 |
34 |
35 | def _pendulum_json(obj):
36 | return obj.for_json()
37 |
38 |
39 | pendulum.DateTime.as_datetime = _pendulum_to_datetime # type: ignore
40 | pendulum.DateTime.as_naive_datetime = _pendulum_to_naive_datetime # type: ignore
41 | pendulum.DateTime.__json__ = _pendulum_json # type: ignore
42 |
--------------------------------------------------------------------------------
/emmett/_shortcuts.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | emmett._shortcuts
4 | -----------------
5 |
6 | Some shortcuts
7 |
8 | :copyright: 2014 Giovanni Barillari
9 | :license: BSD-3-Clause
10 | """
11 |
12 | import hashlib
13 | from uuid import uuid4
14 |
15 |
16 | hashlib_md5 = lambda s: hashlib.md5(bytes(s, "utf8"))
17 | hashlib_sha1 = lambda s: hashlib.sha1(bytes(s, "utf8"))
18 | uuid = lambda: str(uuid4())
19 |
20 |
21 | def to_bytes(obj, charset="utf8", errors="strict"):
22 | if obj is None:
23 | return None
24 | if isinstance(obj, (bytes, bytearray, memoryview)):
25 | return bytes(obj)
26 | if isinstance(obj, str):
27 | return obj.encode(charset, errors)
28 | raise TypeError("Expected bytes")
29 |
30 |
31 | def to_unicode(obj, charset="utf8", errors="strict"):
32 | if obj is None:
33 | return None
34 | if not isinstance(obj, bytes):
35 | return str(obj)
36 | return obj.decode(charset, errors)
37 |
--------------------------------------------------------------------------------
/emmett/asgi/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emmett-framework/emmett/8f206515d0a7b01bf399656383c0b0019ce90065/emmett/asgi/__init__.py
--------------------------------------------------------------------------------
/emmett/asgi/handlers.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | emmett.asgi.handlers
4 | --------------------
5 |
6 | Provides ASGI handlers.
7 |
8 | :copyright: 2014 Giovanni Barillari
9 | :license: BSD-3-Clause
10 | """
11 |
12 | from __future__ import annotations
13 |
14 | from hashlib import md5
15 | from importlib import resources
16 | from typing import Awaitable, Callable
17 |
18 | from emmett_core.http.response import HTTPBytesResponse, HTTPResponse, HTTPStringResponse
19 | from emmett_core.protocols.asgi.handlers import HTTPHandler as _HTTPHandler, RequestCancelled, WSHandler as _WSHandler
20 | from emmett_core.protocols.asgi.typing import Receive, Scope, Send
21 | from emmett_core.utils import cachedprop
22 |
23 | from ..ctx import RequestContext, WSContext, current
24 | from ..debug import debug_handler, smart_traceback
25 | from ..libs.contenttype import contenttype
26 | from .wrappers import Request, Response, Websocket
27 |
28 |
29 | class HTTPHandler(_HTTPHandler):
30 | __slots__ = []
31 | wrapper_cls = Request
32 | response_cls = Response
33 |
34 | @cachedprop
35 | def error_handler(self) -> Callable[[], Awaitable[str]]:
36 | return self._debug_handler if self.app.debug else self.exception_handler
37 |
38 | async def _static_content(self, content: bytes, content_type: str) -> HTTPBytesResponse:
39 | content_len = str(len(content))
40 | return HTTPBytesResponse(
41 | 200,
42 | content,
43 | headers={
44 | "content-type": content_type,
45 | "content-length": content_len,
46 | "last-modified": self._internal_assets_md[1],
47 | "etag": md5(f"{self._internal_assets_md[0]}_{content_len}".encode("utf8")).hexdigest(),
48 | },
49 | )
50 |
51 | def _static_handler(self, scope: Scope, receive: Receive, send: Send) -> Awaitable[HTTPResponse]:
52 | path = scope["emt.path"]
53 | #: handle internal assets
54 | if path.startswith("/__emmett__"):
55 | file_name = path[12:]
56 | if not file_name or file_name.endswith(".html"):
57 | return self._http_response(404)
58 | pkg = None
59 | if "/" in file_name:
60 | pkg, file_name = file_name.split("/", 1)
61 | try:
62 | file_contents = resources.read_binary(f"emmett.assets.{pkg}" if pkg else "emmett.assets", file_name)
63 | except FileNotFoundError:
64 | return self._http_response(404)
65 | return self._static_content(file_contents, contenttype(file_name))
66 | return super()._static_handler(scope, receive, send)
67 |
68 | async def _debug_handler(self) -> str:
69 | current.response.headers._data["content-type"] = "text/html; charset=utf-8"
70 | return debug_handler(smart_traceback(self.app))
71 |
72 | async def dynamic_handler(self, scope: Scope, receive: Receive, send: Send) -> HTTPResponse:
73 | request = Request(
74 | scope,
75 | receive,
76 | send,
77 | max_content_length=self.app.config.request_max_content_length,
78 | max_multipart_size=self.app.config.request_multipart_max_size,
79 | body_timeout=self.app.config.request_body_timeout,
80 | )
81 | response = Response(send)
82 | ctx = RequestContext(self.app, request, response)
83 | ctx_token = current._init_(ctx)
84 | try:
85 | http = await self.router.dispatch(request, response)
86 | except HTTPResponse as http_exception:
87 | http = http_exception
88 | #: render error with handlers if in app
89 | error_handler = self.app.error_handlers.get(http.status_code)
90 | if error_handler:
91 | http = HTTPStringResponse(
92 | http.status_code, await error_handler(), headers=response.headers, cookies=response.cookies
93 | )
94 | except RequestCancelled:
95 | raise
96 | except Exception:
97 | self.app.log.exception("Application exception:")
98 | http = HTTPStringResponse(500, await self.error_handler(), headers=response.headers)
99 | finally:
100 | current._close_(ctx_token)
101 | return http
102 |
103 | async def _exception_handler(self) -> str:
104 | current.response.headers._data["content-type"] = "text/plain"
105 | return "Internal error"
106 |
107 |
108 | class WSHandler(_WSHandler):
109 | __slots__ = []
110 | wrapper_cls = Websocket
111 |
112 | async def dynamic_handler(self, scope: Scope, send: Send):
113 | ctx = WSContext(self.app, Websocket(scope, scope["emt.input"].get, send))
114 | ctx_token = current._init_(ctx)
115 | try:
116 | await self.router.dispatch(ctx.websocket)
117 | finally:
118 | if not scope.get("emt._flow_cancel", False) and ctx.websocket._accepted:
119 | await send({"type": "websocket.close", "code": 1000})
120 | scope["emt._ws_closed"] = True
121 | current._close_(ctx_token)
122 |
--------------------------------------------------------------------------------
/emmett/asgi/wrappers.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | emmett.asgi.wrappers
4 | --------------------
5 |
6 | Provides ASGI request and websocket wrappers
7 |
8 | :copyright: 2014 Giovanni Barillari
9 | :license: BSD-3-Clause
10 | """
11 |
12 | import pendulum
13 | from emmett_core.protocols.asgi.wrappers import Request as _Request, Response as _Response, Websocket as Websocket
14 | from emmett_core.utils import cachedprop
15 |
16 | from ..wrappers.response import ResponseMixin
17 |
18 |
19 | class Request(_Request):
20 | __slots__ = []
21 |
22 | @cachedprop
23 | def now(self) -> pendulum.DateTime:
24 | return pendulum.instance(self._now)
25 |
26 | @cachedprop
27 | def now_local(self) -> pendulum.DateTime:
28 | return self.now.in_timezone(pendulum.local_timezone()) # type: ignore
29 |
30 |
31 | class Response(ResponseMixin, _Response):
32 | __slots__ = []
33 |
--------------------------------------------------------------------------------
/emmett/assets/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emmett-framework/emmett/8f206515d0a7b01bf399656383c0b0019ce90065/emmett/assets/__init__.py
--------------------------------------------------------------------------------
/emmett/assets/debug/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emmett-framework/emmett/8f206515d0a7b01bf399656383c0b0019ce90065/emmett/assets/debug/__init__.py
--------------------------------------------------------------------------------
/emmett/assets/debug/shBrushPython.js:
--------------------------------------------------------------------------------
1 | /**
2 | * SyntaxHighlighter
3 | * http://alexgorbatchev.com/SyntaxHighlighter
4 | *
5 | * SyntaxHighlighter is donationware. If you are using it, please donate.
6 | * http://alexgorbatchev.com/SyntaxHighlighter/donate.html
7 | *
8 | * @version
9 | * 3.0.83 (July 02 2010)
10 | *
11 | * @copyright
12 | * Copyright (C) 2004-2010 Alex Gorbatchev.
13 | *
14 | * @license
15 | * Dual licensed under the MIT and GPL licenses.
16 | */
17 | ;(function()
18 | {
19 | // CommonJS
20 | typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null;
21 |
22 | function Brush()
23 | {
24 | // Contributed by Gheorghe Milas and Ahmad Sherif
25 |
26 | var keywords = 'and assert break class continue def del elif else ' +
27 | 'except exec finally for from global if import in is ' +
28 | 'lambda not or pass print raise return try yield while';
29 |
30 | var funcs = '__import__ abs all any apply basestring bin bool buffer callable ' +
31 | 'chr classmethod cmp coerce compile complex delattr dict dir ' +
32 | 'divmod enumerate eval execfile file filter float format frozenset ' +
33 | 'getattr globals hasattr hash help hex id input int intern ' +
34 | 'isinstance issubclass iter len list locals long map max min next ' +
35 | 'object oct open ord pow print property range raw_input reduce ' +
36 | 'reload repr reversed round set setattr slice sorted staticmethod ' +
37 | 'str sum super tuple type type unichr unicode vars xrange zip';
38 |
39 | var special = 'None True False self cls class_';
40 |
41 | this.regexList = [
42 | { regex: SyntaxHighlighter.regexLib.singleLinePerlComments, css: 'comments' },
43 | { regex: /^\s*@\w+/gm, css: 'decorator' },
44 | { regex: /(['\"]{3})([^\1])*?\1/gm, css: 'comments' },
45 | { regex: /"(?!")(?:\.|\\\"|[^\""\n])*"/gm, css: 'string' },
46 | { regex: /'(?!')(?:\.|(\\\')|[^\''\n])*'/gm, css: 'string' },
47 | { regex: /\+|\-|\*|\/|\%|=|==/gm, css: 'keyword' },
48 | { regex: /\b\d+\.?\w*/g, css: 'value' },
49 | { regex: new RegExp(this.getKeywords(funcs), 'gmi'), css: 'functions' },
50 | { regex: new RegExp(this.getKeywords(keywords), 'gm'), css: 'keyword' },
51 | { regex: new RegExp(this.getKeywords(special), 'gm'), css: 'color1' }
52 | ];
53 |
54 | this.forHtmlScript(SyntaxHighlighter.regexLib.aspScriptTags);
55 | };
56 |
57 | Brush.prototype = new SyntaxHighlighter.Highlighter();
58 | Brush.aliases = ['py', 'python'];
59 |
60 | SyntaxHighlighter.brushes.Python = Brush;
61 |
62 | // CommonJS
63 | typeof(exports) != 'undefined' ? exports.Brush = Brush : null;
64 | })();
65 |
--------------------------------------------------------------------------------
/emmett/assets/debug/shTheme.css:
--------------------------------------------------------------------------------
1 | .syntaxhighlighter {
2 | background-color: white !important;
3 | }
4 | .syntaxhighlighter .line.alt1 {
5 | background-color: white !important;
6 | }
7 | .syntaxhighlighter .line.alt2 {
8 | background-color: white !important;
9 | }
10 | .syntaxhighlighter .line.highlighted.alt1, .syntaxhighlighter .line.highlighted.alt2 {
11 | background-color: #ebdde2 !important;
12 | }
13 | .syntaxhighlighter .line.highlighted.number {
14 | color: black !important;
15 | }
16 | .syntaxhighlighter table caption {
17 | color: black !important;
18 | }
19 | .syntaxhighlighter .gutter {
20 | color: #afafaf !important;
21 | }
22 | .syntaxhighlighter .gutter .line {
23 | border-right: 3px solid orange !important;
24 | }
25 | .syntaxhighlighter .gutter .line.highlighted {
26 | background-color: orange !important;
27 | color: white !important;
28 | }
29 | .syntaxhighlighter.printing .line .content {
30 | border: none !important;
31 | }
32 | .syntaxhighlighter.collapsed {
33 | overflow: visible !important;
34 | }
35 | .syntaxhighlighter.collapsed .toolbar {
36 | color: blue !important;
37 | background: white !important;
38 | border: 1px solid #6ce26c !important;
39 | }
40 | .syntaxhighlighter.collapsed .toolbar a {
41 | color: blue !important;
42 | }
43 | .syntaxhighlighter.collapsed .toolbar a:hover {
44 | color: red !important;
45 | }
46 | .syntaxhighlighter .toolbar {
47 | color: white !important;
48 | background: #6ce26c !important;
49 | border: none !important;
50 | }
51 | .syntaxhighlighter .toolbar a {
52 | color: white !important;
53 | }
54 | .syntaxhighlighter .toolbar a:hover {
55 | color: black !important;
56 | }
57 | .syntaxhighlighter .plain, .syntaxhighlighter .plain a {
58 | color: black !important;
59 | }
60 | .syntaxhighlighter .comments, .syntaxhighlighter .comments a {
61 | color: #008200 !important;
62 | }
63 | .syntaxhighlighter .string, .syntaxhighlighter .string a {
64 | color: orange !important;
65 | }
66 | .syntaxhighlighter .keyword {
67 | color: #18536D !important;
68 | }
69 | .syntaxhighlighter .preprocessor {
70 | color: gray !important;
71 | }
72 | .syntaxhighlighter .variable {
73 | color: #aa7700 !important;
74 | }
75 | .syntaxhighlighter .value {
76 | color: #009900 !important;
77 | }
78 | .syntaxhighlighter .functions {
79 | color: #ff5c1f !important;
80 | }
81 | .syntaxhighlighter .constants {
82 | color: #0066cc !important;
83 | }
84 | .syntaxhighlighter .script {
85 | font-weight: bold !important;
86 | color: #006699 !important;
87 | background-color: none !important;
88 | }
89 | .syntaxhighlighter .color1, .syntaxhighlighter .color1 a {
90 | color: gray !important;
91 | }
92 | .syntaxhighlighter .color2, .syntaxhighlighter .color2 a {
93 | color: #ff1493 !important;
94 | }
95 | .syntaxhighlighter .color3, .syntaxhighlighter .color3 a {
96 | color: red !important;
97 | }
98 |
99 | .syntaxhighlighter .keyword {
100 | font-weight: bold !important;
101 | }
102 |
--------------------------------------------------------------------------------
/emmett/assets/debug/view.css:
--------------------------------------------------------------------------------
1 | body, html {
2 | margin: 0;
3 | font-family: arial, sans-serif
4 | }
5 |
6 | .header {
7 | background-color: orange;
8 | margin: 0;
9 | padding: 1%;
10 | color: #fff;
11 | font-size: 26px;
12 | font-weight: 600;
13 | }
14 |
15 | .container {
16 | padding: 1%;
17 | }
18 |
19 | h3, h5 {
20 | color: orange;
21 | }
22 |
23 | .traceback {
24 | border: 1px solid #ddd;
25 | margin-top: 5px;
26 | margin-bottom: 5px;
27 | }
28 |
29 | a, a:active, a:hover, a:visited {
30 | text-decoration: none;
31 | color: black;
32 | }
33 |
34 | .frameList {
35 | list-style: none;
36 | }
37 |
38 | .framefile {
39 | margin-top: 45px;
40 | border-bottom: 1px solid #ddd;
41 | }
42 |
43 | .frameth {
44 | border-right: 1px solid #ddd;
45 | padding-right:10px;
46 | }
47 |
48 | .frametd {
49 | padding-left: 10px;
50 | display: block;
51 | width:90%;
52 | word-wrap: break-word;
53 | }
54 |
--------------------------------------------------------------------------------
/emmett/assets/debug/view.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Error
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | {{=tb.exception_type}} in {{=tb.frames[-1].rendered_filename}}
15 |
17 | by {{=comment.user.first_name}} on {{=comment.date}} 18 |