├── emmett
├── asgi
│ ├── __init__.py
│ ├── wrappers.py
│ └── handlers.py
├── libs
│ ├── __init__.py
│ └── portalocker.py
├── rsgi
│ ├── __init__.py
│ ├── wrappers.py
│ └── handlers.py
├── assets
│ ├── __init__.py
│ ├── debug
│ │ ├── __init__.py
│ │ ├── view.css
│ │ ├── shBrushPython.js
│ │ ├── shTheme.css
│ │ └── view.html
│ └── helpers.js
├── language
│ ├── __init__.py
│ ├── translator.py
│ └── helpers.py
├── routing
│ ├── __init__.py
│ ├── urls.py
│ ├── router.py
│ ├── rules.py
│ ├── routes.py
│ └── response.py
├── wrappers
│ ├── __init__.py
│ ├── websocket.py
│ ├── request.py
│ └── response.py
├── templating
│ ├── __init__.py
│ ├── lexers.py
│ └── templater.py
├── __version__.py
├── tools
│ ├── auth
│ │ ├── __init__.py
│ │ └── forms.py
│ ├── __init__.py
│ ├── stream.py
│ ├── decorators.py
│ └── service.py
├── orm
│ ├── migrations
│ │ ├── __init__.py
│ │ ├── migration.tmpl
│ │ ├── exceptions.py
│ │ ├── utils.py
│ │ ├── base.py
│ │ └── helpers.py
│ ├── engines
│ │ ├── __init__.py
│ │ └── sqlite.py
│ ├── __init__.py
│ ├── errors.py
│ ├── wrappers.py
│ ├── geo.py
│ ├── _patches.py
│ ├── apis.py
│ └── transactions.py
├── parsers.py
├── __main__.py
├── __init__.py
├── sessions.py
├── extensions.py
├── serializers.py
├── _shortcuts.py
├── locals.py
├── _internal.py
├── testing.py
├── ctx.py
├── helpers.py
├── http.py
├── pipeline.py
├── html.py
├── security.py
├── utils.py
├── datastructures.py
├── cache.py
└── validators
│ └── process.py
├── .github
├── FUNDING.yml
├── dependabot.yml
└── workflows
│ ├── lint.yml
│ ├── publish.yml
│ └── tests.yml
├── tests
├── languages
│ ├── de.json
│ ├── it.json
│ └── ru.json
├── templates
│ ├── auth
│ │ └── auth.html
│ ├── test.html
│ └── layout.html
├── test_translator.py
├── test_orm_connections.py
├── test_wrappers.py
├── test_cache.py
├── test_logger.py
├── test_utils.py
├── helpers.py
├── test_templates.py
└── test_orm_transactions.py
├── artwork
├── logo-bwb-wb-sc.png
├── logo-bwb-xb-sc.png
├── logo-bwb-xb-xl.png
└── LICENSE
├── examples
└── bloggy
│ ├── templates
│ ├── new_post.html
│ ├── auth
│ │ └── auth.html
│ ├── index.html
│ ├── one.html
│ └── layout.html
│ ├── static
│ └── style.css
│ ├── tests.py
│ ├── app.py
│ └── migrations
│ └── 9d6518b3cdc2_first_migration.py
├── BACKERS.md
├── .gitignore
├── Makefile
├── docs
├── tree.yml
├── installation.md
├── websocket.md
├── deployment.md
├── cli.md
├── services.md
├── html.md
├── orm.md
├── mailer.md
├── languages.md
├── debug_and_logging.md
├── patterns.md
├── sessions.md
└── foreword.md
├── LICENSE
├── README.md
└── pyproject.toml
/emmett/asgi/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/emmett/libs/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/emmett/rsgi/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/emmett/assets/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/emmett/language/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/emmett/routing/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/emmett/wrappers/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/emmett/assets/debug/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/emmett/templating/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/emmett/__version__.py:
--------------------------------------------------------------------------------
1 | __version__ = "2.7.2"
2 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: [gi0baro]
2 | polar: emmett-framework
3 |
--------------------------------------------------------------------------------
/tests/languages/de.json:
--------------------------------------------------------------------------------
1 | {
2 | "partly cloudy": "teilweise bewölkt"
3 | }
4 |
--------------------------------------------------------------------------------
/tests/languages/it.json:
--------------------------------------------------------------------------------
1 | {
2 | "partly cloudy": "nuvolosità variabile"
3 | }
4 |
--------------------------------------------------------------------------------
/tests/languages/ru.json:
--------------------------------------------------------------------------------
1 | {
2 | "partly cloudy": "переменная облачность"
3 | }
4 |
--------------------------------------------------------------------------------
/emmett/tools/auth/__init__.py:
--------------------------------------------------------------------------------
1 | from .apis import Auth
2 | from .models import AuthUser
3 |
--------------------------------------------------------------------------------
/emmett/orm/migrations/__init__.py:
--------------------------------------------------------------------------------
1 | from .base import Column, Migration
2 | from .operations import *
3 |
--------------------------------------------------------------------------------
/artwork/logo-bwb-wb-sc.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emmett-framework/emmett/HEAD/artwork/logo-bwb-wb-sc.png
--------------------------------------------------------------------------------
/artwork/logo-bwb-xb-sc.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emmett-framework/emmett/HEAD/artwork/logo-bwb-xb-sc.png
--------------------------------------------------------------------------------
/artwork/logo-bwb-xb-xl.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emmett-framework/emmett/HEAD/artwork/logo-bwb-xb-xl.png
--------------------------------------------------------------------------------
/emmett/orm/engines/__init__.py:
--------------------------------------------------------------------------------
1 | from pydal.adapters import adapters
2 |
3 | from . import postgres, sqlite
4 |
--------------------------------------------------------------------------------
/examples/bloggy/templates/new_post.html:
--------------------------------------------------------------------------------
1 | {{extend 'layout.html'}}
2 |
3 |
11 |
Test
12 |
15 | {{block main}}
16 | {{include}}
17 | {{end}}
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/emmett/sessions.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | emmett.sessions
4 | ---------------
5 |
6 | Provides session managers for applications.
7 |
8 | :copyright: 2014 Giovanni Barillari
9 | :license: BSD-3-Clause
10 | """
11 |
12 | from __future__ import annotations
13 |
14 | from emmett_core.sessions import SessionManager as _SessionManager
15 |
16 | from .ctx import current
17 |
18 |
19 | class SessionManager(_SessionManager):
20 | @classmethod
21 | def _build_pipe(cls, handler_cls, *args, **kwargs):
22 | cls._pipe = handler_cls(current, *args, **kwargs)
23 | return cls._pipe
24 |
--------------------------------------------------------------------------------
/emmett/orm/migrations/migration.tmpl:
--------------------------------------------------------------------------------
1 | """{{=message}}
2 |
3 | Migration ID: {{=up_migration}}
4 | Revises: {{=down_migration_str}}
5 | Creation Date: {{=creation_date}}
6 |
7 | """
8 |
9 | from emmett.orm import migrations
10 |
11 |
12 | class Migration(migrations.Migration):
13 | revision = {{=asis("%r" % up_migration)}}
14 | revises = {{=asis(down_migration)}}
15 |
16 | def up(self):
17 | {{for upgrade in upgrades:}}
18 | {{=asis(upgrade)}}
19 | {{pass}}
20 |
21 | def down(self):
22 | {{for downgrade in downgrades:}}
23 | {{=asis(downgrade)}}
24 | {{pass}}
25 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/emmett/orm/__init__.py:
--------------------------------------------------------------------------------
1 | from . import _patches
2 | from .adapters import adapters as adapters_registry
3 | from .apis import (
4 | after_commit,
5 | after_delete,
6 | after_destroy,
7 | after_insert,
8 | after_save,
9 | after_update,
10 | before_commit,
11 | before_delete,
12 | before_destroy,
13 | before_insert,
14 | before_save,
15 | before_update,
16 | belongs_to,
17 | compute,
18 | has_many,
19 | has_one,
20 | refers_to,
21 | rowattr,
22 | rowmethod,
23 | scope,
24 | )
25 | from .base import Database
26 | from .models import Model
27 | from .objects import Field, TransactionOps
28 |
--------------------------------------------------------------------------------
/.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@v5
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 |
--------------------------------------------------------------------------------
/emmett/orm/errors.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | emmett.orm.errors
4 | -----------------
5 |
6 | Provides some error wrappers.
7 |
8 | :copyright: 2014 Giovanni Barillari
9 | :license: BSD-3-Clause
10 | """
11 |
12 |
13 | class MaxConnectionsExceeded(RuntimeError):
14 | def __init__(self):
15 | super().__init__("Exceeded maximum connections")
16 |
17 |
18 | class MissingFieldsForCompute(RuntimeError): ...
19 |
20 |
21 | class SaveException(RuntimeError): ...
22 |
23 |
24 | class InsertFailureOnSave(SaveException): ...
25 |
26 |
27 | class UpdateFailureOnSave(SaveException): ...
28 |
29 |
30 | class DestroyException(RuntimeError): ...
31 |
32 |
33 | class ValidationError(RuntimeError): ...
34 |
--------------------------------------------------------------------------------
/emmett/extensions.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | emmett.extensions
4 | -----------------
5 |
6 | Provides base classes to create extensions.
7 |
8 | :copyright: 2014 Giovanni Barillari
9 | :license: BSD-3-Clause
10 | """
11 |
12 | from __future__ import annotations
13 |
14 | from enum import Enum
15 |
16 | from emmett_core.extensions import Extension as Extension, listen_signal as listen_signal
17 |
18 |
19 | class Signals(str, Enum):
20 | __str__ = lambda v: v.value
21 |
22 | after_database = "after_database"
23 | after_loop = "after_loop"
24 | after_route = "after_route"
25 | before_database = "before_database"
26 | before_route = "before_route"
27 | before_routes = "before_routes"
28 |
--------------------------------------------------------------------------------
/tests/test_translator.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | tests.translator
4 | ----------------
5 |
6 | Test Emmett translator module
7 | """
8 |
9 | import pytest
10 |
11 | from emmett import App
12 | from emmett.ctx import current
13 | from emmett.locals import T
14 |
15 |
16 | @pytest.fixture(scope="module")
17 | def app():
18 | return App(__name__)
19 |
20 |
21 | def _make_translation(language):
22 | return str(T("partly cloudy", lang=language))
23 |
24 |
25 | def test_translation(app):
26 | current.language = "en"
27 | assert _make_translation("it") == "nuvolosità variabile"
28 | assert _make_translation("de") == "teilweise bewölkt"
29 | assert _make_translation("ru") == "переменная облачность"
30 |
--------------------------------------------------------------------------------
/emmett/wrappers/request.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | emmett.wrappers.request
4 | -----------------------
5 |
6 | Provides http request wrappers.
7 |
8 | :copyright: 2014 Giovanni Barillari
9 | :license: BSD-3-Clause
10 | """
11 |
12 | import pendulum
13 | from emmett_core.http.wrappers.request import Request as _Request
14 | from emmett_core.utils import cachedprop
15 |
16 |
17 | class Request(_Request):
18 | __slots__ = []
19 |
20 | # method: str
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 |
--------------------------------------------------------------------------------
/examples/bloggy/templates/layout.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
Bloggy
12 |
13 | {{if not current.session.auth:}}
14 |
log in
15 | {{else:}}
16 |
log out
17 | {{pass}}
18 |
19 | {{block main}}
20 | {{include}}
21 | {{end}}
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/.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@v5
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 |
--------------------------------------------------------------------------------
/examples/bloggy/static/style.css:
--------------------------------------------------------------------------------
1 | body { font-family: sans-serif; background: #eee; }
2 | a, h1, h2 { color: #377ba8; }
3 | h1, h2, h4, h5 { font-family: 'Georgia', serif; }
4 | h1 { border-bottom: 2px solid #eee; }
5 | h2 { font-size: 1.2em; }
6 |
7 | .page { margin: 2em auto; width: 35em; border: 5px solid #ccc;
8 | padding: 0.8em; background: white; }
9 | .title { text-decoration: none; }
10 | .posts { list-style: none; margin: 0; padding: 0; }
11 | .posts li { margin: 0.8em 1.2em; }
12 | .posts li h2 { margin-left: -1em; }
13 | .posts li hr { margin-left: -0.8em; }
14 | .nav { text-align: right; font-size: 0.8em; padding: 0.3em;
15 | margin-bottom: 1em; background: #fafafa; }
16 |
--------------------------------------------------------------------------------
/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/rsgi/wrappers.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | emmett.rsgi.wrappers
4 | --------------------
5 |
6 | Provides RSGI 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.rsgi.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/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/serializers.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | emmett.serializers
4 | ------------------
5 |
6 | :copyright: 2014 Giovanni Barillari
7 | :license: BSD-3-Clause
8 | """
9 |
10 | from emmett_core.serializers import Serializers as Serializers
11 |
12 | from .html import htmlescape, tag
13 |
14 |
15 | def xml_encode(value, key=None, quote=True):
16 | if hasattr(value, "__xml__"):
17 | return value.__xml__(key, quote)
18 | if isinstance(value, dict):
19 | return tag[key](*[tag[k](xml_encode(v, None, quote)) for k, v in value.items()])
20 | if isinstance(value, list):
21 | return tag[key](*[tag[item](xml_encode(item, None, quote)) for item in value])
22 | return htmlescape(value)
23 |
24 |
25 | @Serializers.register_for("xml")
26 | def xml(value, encoding="UTF-8", key="document", quote=True):
27 | rv = ('' % encoding) + str(xml_encode(value, key, quote))
28 | return rv
29 |
--------------------------------------------------------------------------------
/tests/test_orm_connections.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | tests.orm_connections
4 | ---------------------
5 |
6 | Test pyDAL connection implementation over Emmett.
7 | """
8 |
9 | import pytest
10 |
11 | from emmett import App, sdict
12 | from emmett.orm import Database
13 |
14 |
15 | @pytest.fixture(scope="module")
16 | def db():
17 | app = App(__name__)
18 | db = Database(app, config=sdict(uri="sqlite:memory", auto_migrate=True, auto_connect=False))
19 | return db
20 |
21 |
22 | def test_connection_ctx_sync(db):
23 | assert not db._adapter.connection
24 |
25 | with db.connection():
26 | assert db._adapter.connection
27 |
28 | assert not db._adapter.connection
29 |
30 |
31 | @pytest.mark.asyncio
32 | async def test_connection_ctx_loop(db):
33 | assert not db._adapter.connection
34 |
35 | async with db.connection():
36 | assert db._adapter.connection
37 |
38 | assert not db._adapter.connection
39 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/tests/test_wrappers.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | tests.wrappers
4 | --------------
5 |
6 | Test Emmett wrappers module
7 | """
8 |
9 | from emmett_core.protocols.rsgi.test_client.scope import ScopeBuilder
10 | from helpers import current_ctx
11 |
12 | from emmett.rsgi.wrappers import Request, Response
13 |
14 |
15 | def test_request():
16 | scope, _ = ScopeBuilder(
17 | path="/?foo=bar",
18 | method="GET",
19 | ).get_data()
20 | request = Request(scope, None, None)
21 |
22 | assert request.query_params == {"foo": "bar"}
23 | assert request.client == "127.0.0.1"
24 |
25 |
26 | def test_response():
27 | response = Response(None)
28 |
29 | assert response.status == 200
30 | assert response.headers["content-type"] == "text/plain"
31 |
32 |
33 | def test_req_ctx():
34 | with current_ctx("/?foo=bar") as ctx:
35 | assert isinstance(ctx.request, Request)
36 | assert isinstance(ctx.response, Response)
37 |
--------------------------------------------------------------------------------
/tests/test_cache.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | tests.cache
4 | -----------
5 |
6 | Test Emmett cache module
7 | """
8 |
9 | import pytest
10 |
11 | from emmett import App
12 | from emmett.cache import DiskCache
13 |
14 |
15 | async def _await_2():
16 | return 2
17 |
18 |
19 | async def _await_3():
20 | return 3
21 |
22 |
23 | @pytest.mark.asyncio
24 | async def test_diskcache():
25 | App(__name__)
26 |
27 | disk_cache = DiskCache()
28 | assert disk_cache._threshold == 500
29 |
30 | assert disk_cache("test", lambda: 2) == 2
31 | assert disk_cache("test", lambda: 3, 300) == 2
32 |
33 | assert await disk_cache("test_loop", _await_2) == 2
34 | assert await disk_cache("test_loop", _await_3, 300) == 2
35 |
36 | disk_cache.set("test", 3)
37 | assert disk_cache.get("test") == 3
38 |
39 | disk_cache.set("test", 4, 300)
40 | assert disk_cache.get("test") == 4
41 |
42 | disk_cache.clear()
43 | assert disk_cache.get("test") is None
44 |
--------------------------------------------------------------------------------
/tests/test_logger.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | tests.logger
4 | ------------
5 |
6 | Test Emmett logging module
7 | """
8 |
9 | import logging
10 |
11 | from emmett_core import log as logger
12 |
13 | from emmett import App, sdict
14 |
15 |
16 | def _call_create_logger(app):
17 | return logger.create_logger(app)
18 |
19 |
20 | def test_user_assign_valid_level():
21 | app = App(__name__)
22 | app.config.logging.pytest = sdict(level="info")
23 | result = _call_create_logger(app)
24 | assert result.handlers[-1].level == logging.INFO
25 |
26 |
27 | def test_user_assign_invaild_level():
28 | app = App(__name__)
29 | app.config.logging.pytest = sdict(level="invalid")
30 | result = _call_create_logger(app)
31 | assert result.handlers[-1].level == logging.WARNING
32 |
33 |
34 | def test_user_no_assign_level():
35 | app = App(__name__)
36 | app.config.logging.pytest = sdict()
37 | result = _call_create_logger(app)
38 | assert result.handlers[-1].level == logging.WARNING
39 |
--------------------------------------------------------------------------------
/emmett/language/translator.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | emmett.language.translator
4 | --------------------------
5 |
6 | Severus translator implementation for Emmett.
7 |
8 | :copyright: 2014 Giovanni Barillari
9 | :license: BSD-3-Clause
10 | """
11 |
12 | from __future__ import annotations
13 |
14 | from typing import Optional
15 |
16 | from severus.ctx import set_context
17 | from severus.translator import Translator as _Translator
18 |
19 | from ..ctx import current
20 |
21 |
22 | class Translator(_Translator):
23 | __slots__ = []
24 |
25 | def __init__(self, *args, **kwargs):
26 | super().__init__(*args, **kwargs)
27 | set_context(self)
28 |
29 | def _update_config(self, default_language: str):
30 | self._default_language = default_language
31 | self._langmap.clear()
32 | self._languages.clear()
33 | self._build_languages()
34 |
35 | def _get_best_language(self, lang: Optional[str] = None) -> str:
36 | return self._langmap.get(lang or current.language, self._default_language)
37 |
--------------------------------------------------------------------------------
/emmett/locals.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | emmett.locals
4 | -------------
5 |
6 | Provides shortcuts to `current` object.
7 |
8 | :copyright: 2014 Giovanni Barillari
9 | :license: BSD-3-Clause
10 | """
11 |
12 | from typing import Optional, cast
13 |
14 | from emmett_core._internal import ContextVarProxy as _VProxy, ObjectProxy as _OProxy
15 | from pendulum import DateTime
16 |
17 | from .ctx import _ctxv, current
18 | from .datastructures import sdict
19 | from .language.translator import Translator
20 | from .wrappers.request import Request
21 | from .wrappers.response import Response
22 | from .wrappers.websocket import Websocket
23 |
24 |
25 | request = cast(Request, _VProxy[Request](_ctxv, "request"))
26 | response = cast(Response, _VProxy[Response](_ctxv, "response"))
27 | session = cast(Optional[sdict], _VProxy[Optional[sdict]](_ctxv, "session"))
28 | websocket = cast(Websocket, _VProxy[Websocket](_ctxv, "websocket"))
29 | T = cast(Translator, _OProxy[Translator](current, "T"))
30 |
31 |
32 | def now() -> DateTime:
33 | return current.now
34 |
--------------------------------------------------------------------------------
/emmett/tools/decorators.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | emmett.tools.decorators
4 | -----------------------
5 |
6 | Provides requires and service decorators.
7 |
8 | :copyright: 2014 Giovanni Barillari
9 | :license: BSD-3-Clause
10 | """
11 |
12 | from emmett_core.pipeline.dyn import (
13 | ServicePipeBuilder as _ServicePipeBuilder,
14 | requires as _requires,
15 | service as _service,
16 | sse as _sse,
17 | stream as _stream,
18 | )
19 |
20 | from ..pipeline import RequirePipe
21 | from .service import JSONServicePipe, XMLServicePipe
22 | from .stream import SSEPipe, StreamPipe
23 |
24 |
25 | class ServicePipeBuilder(_ServicePipeBuilder):
26 | _pipe_cls = {"json": JSONServicePipe, "xml": XMLServicePipe}
27 |
28 |
29 | class requires(_requires):
30 | _pipe_cls = RequirePipe
31 |
32 |
33 | class stream(_stream):
34 | _pipe_cls = StreamPipe
35 |
36 |
37 | class sse(_sse):
38 | _pipe_cls = SSEPipe
39 |
40 |
41 | class service(_service):
42 | _inner_builder = ServicePipeBuilder()
43 |
44 | @staticmethod
45 | def xml(f):
46 | return service("xml")(f)
47 |
--------------------------------------------------------------------------------
/emmett/orm/wrappers.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | emmett.orm.wrappers
4 | -------------------
5 |
6 | Provides ORM wrappers utilities.
7 |
8 | :copyright: 2014 Giovanni Barillari
9 | :license: BSD-3-Clause
10 | """
11 |
12 | from .helpers import RelationBuilder
13 | from .objects import HasManySet, HasManyViaSet, HasOneSet, HasOneViaSet
14 |
15 |
16 | class Wrapper(object):
17 | def __init__(self, ref):
18 | self.__name__ = ref.name
19 | self.ref = ref
20 |
21 |
22 | class HasOneWrap(Wrapper):
23 | def __call__(self, model, row):
24 | return HasOneSet(model.db, RelationBuilder(self.ref, model), row)
25 |
26 |
27 | class HasOneViaWrap(Wrapper):
28 | def __call__(self, model, row):
29 | return HasOneViaSet(model.db, RelationBuilder(self.ref, model), row)
30 |
31 |
32 | class HasManyWrap(Wrapper):
33 | def __call__(self, model, row):
34 | return HasManySet(model.db, RelationBuilder(self.ref, model), row)
35 |
36 |
37 | class HasManyViaWrap(Wrapper):
38 | def __call__(self, model, row):
39 | return HasManyViaSet(model.db, RelationBuilder(self.ref, model), row)
40 |
--------------------------------------------------------------------------------
/emmett/routing/router.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | emmett.routing.router
4 | ---------------------
5 |
6 | Provides router implementations.
7 |
8 | :copyright: 2014 Giovanni Barillari
9 | :license: BSD-3-Clause
10 | """
11 |
12 | from __future__ import annotations
13 |
14 | from emmett_core.routing.router import (
15 | HTTPRouter as _HTTPRouter,
16 | RoutingCtx as RoutingCtx,
17 | RoutingCtxGroup as RoutingCtxGroup,
18 | WebsocketRouter as WebsocketRouter,
19 | )
20 |
21 | from .response import AutoResponseBuilder, SnippetResponseBuilder, TemplateResponseBuilder
22 | from .rules import HTTPRoutingRule
23 |
24 |
25 | class HTTPRouter(_HTTPRouter):
26 | __slots__ = ["injectors"]
27 |
28 | _routing_rule_cls = HTTPRoutingRule
29 | _outputs = {
30 | **_HTTPRouter._outputs,
31 | **{
32 | "auto": AutoResponseBuilder,
33 | "template": TemplateResponseBuilder,
34 | "snippet": SnippetResponseBuilder,
35 | },
36 | }
37 |
38 | def __init__(self, *args, **kwargs):
39 | super().__init__(*args, **kwargs)
40 | self.injectors = []
41 |
--------------------------------------------------------------------------------
/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/testing.py:
--------------------------------------------------------------------------------
1 | from emmett_core.protocols.rsgi.test_client.client import (
2 | ClientContext as _ClientContext,
3 | ClientHTTPHandlerMixin,
4 | EmmettTestClient as _EmmettTestClient,
5 | )
6 |
7 | from .ctx import current
8 | from .rsgi.handlers import HTTPHandler
9 | from .rsgi.wrappers import Response
10 |
11 |
12 | class ClientContextResponse(Response):
13 | def __init__(self, original_response: Response):
14 | super().__init__(original_response._proto)
15 | self.status = original_response.status
16 | self.headers._data.update(original_response.headers._data)
17 | self.cookies.update(original_response.cookies.copy())
18 | self.__dict__.update(original_response.__dict__)
19 |
20 |
21 | class ClientContext(_ClientContext):
22 | _response_wrap_cls = ClientContextResponse
23 |
24 | def __init__(self, ctx):
25 | super().__init__(ctx)
26 | self.T = current.T
27 |
28 |
29 | class ClientHTTPHandler(ClientHTTPHandlerMixin, HTTPHandler):
30 | _client_ctx_cls = ClientContext
31 |
32 |
33 | class EmmettTestClient(_EmmettTestClient):
34 | _current = current
35 | _handler_cls = ClientHTTPHandler
36 |
--------------------------------------------------------------------------------
/emmett/orm/migrations/exceptions.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | emmett.orm.migrations.exceptions
4 | --------------------------------
5 |
6 | Provides exceptions for migration operations.
7 |
8 | :copyright: 2014 Giovanni Barillari
9 | :license: BSD-3-Clause
10 | """
11 |
12 |
13 | class RevisionError(Exception):
14 | pass
15 |
16 |
17 | class RangeNotAncestorError(RevisionError):
18 | def __init__(self, lower, upper):
19 | self.lower = lower
20 | self.upper = upper
21 | super(RangeNotAncestorError, self).__init__(
22 | "Revision %s is not an ancestor of revision %s" % (lower or "base", upper or "base")
23 | )
24 |
25 |
26 | class MultipleHeads(RevisionError):
27 | def __init__(self, heads, argument):
28 | self.heads = heads
29 | self.argument = argument
30 | super(MultipleHeads, self).__init__(
31 | "Multiple heads are present for given argument '%s'; %s" % (argument, ", ".join(heads))
32 | )
33 |
34 |
35 | class ResolutionError(RevisionError):
36 | def __init__(self, message, argument):
37 | super(ResolutionError, self).__init__(message)
38 | self.argument = argument
39 |
--------------------------------------------------------------------------------
/emmett/language/helpers.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | emmett.language.helpers
4 | -----------------------
5 |
6 | Translation helpers.
7 |
8 | :copyright: 2014 Giovanni Barillari
9 | :license: BSD-3-Clause
10 | """
11 |
12 | import re
13 |
14 | from emmett_core.http.headers import Accept
15 | from severus.datastructures import Tstr as _Tstr
16 |
17 |
18 | class Tstr(_Tstr):
19 | __slots__ = []
20 |
21 | def __getstate__(self):
22 | return {"text": self.text, "lang": self.lang, "args": self.args, "kwargs": self.kwargs}
23 |
24 | def __setstate__(self, state):
25 | self.text = state["text"]
26 | self.lang = state["lang"]
27 | self.args = state["args"]
28 | self.kwargs = state["kwargs"]
29 |
30 | def __getattr__(self, name):
31 | return getattr(str(self), name)
32 |
33 | def __json__(self):
34 | return str(self)
35 |
36 |
37 | class LanguageAccept(Accept):
38 | regex_locale_delim = re.compile(r"[_-]")
39 |
40 | def _value_matches(self, value, item):
41 | def _normalize(language):
42 | return self.regex_locale_delim.split(language.lower())[0]
43 |
44 | return item == "*" or _normalize(value) == _normalize(item)
45 |
--------------------------------------------------------------------------------
/emmett/orm/engines/sqlite.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | emmett.orm.engines.sqlite
4 | -------------------------
5 |
6 | Provides ORM SQLite engine specific features.
7 |
8 | :copyright: 2014 Giovanni Barillari
9 | :license: BSD-3-Clause
10 | """
11 |
12 | from pydal.adapters.sqlite import SQLite as _SQLite
13 |
14 | from . import adapters
15 |
16 |
17 | @adapters.register_for("sqlite", "sqlite:memory")
18 | class SQLite(_SQLite):
19 | def _initialize_(self, do_connect):
20 | super()._initialize_(do_connect)
21 | self.driver_args["isolation_level"] = None
22 |
23 | def begin(self, lock_type=None):
24 | statement = "BEGIN %s;" % lock_type if lock_type else "BEGIN;"
25 | self.execute(statement)
26 |
27 | def delete(self, table, query):
28 | deleted = [x[table._id.name] for x in self.db(query).select(table._id)] if table._id else []
29 | counter = super(_SQLite, self).delete(table, query)
30 | if table._id and counter:
31 | for field in table._referenced_by:
32 | if field.type == "reference " + table._dalname and field.ondelete == "CASCADE":
33 | self.db(field.belongs(deleted)).delete()
34 | return counter
35 |
--------------------------------------------------------------------------------
/emmett/tools/service.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | emmett.tools.service
4 | --------------------
5 |
6 | Provides the services handler.
7 |
8 | :copyright: 2014 Giovanni Barillari
9 | :license: BSD-3-Clause
10 | """
11 |
12 | from emmett_core.pipeline.extras import JSONPipe
13 |
14 | from ..ctx import current
15 | from ..pipeline import Pipe
16 | from ..serializers import Serializers
17 |
18 |
19 | class JSONServicePipe(JSONPipe):
20 | __slots__ = []
21 | _current = current
22 |
23 |
24 | class XMLServicePipe(Pipe):
25 | __slots__ = ["encoder"]
26 | output = "str"
27 |
28 | def __init__(self):
29 | self.encoder = Serializers.get_for("xml")
30 |
31 | async def pipe_request(self, next_pipe, **kwargs):
32 | current.response.headers._data["content-type"] = "text/xml"
33 | return self.encoder(await next_pipe(**kwargs))
34 |
35 | def on_send(self, data):
36 | return self.encoder(data)
37 |
38 |
39 | def ServicePipe(procedure: str) -> Pipe:
40 | pipe_cls = {"json": JSONServicePipe, "xml": XMLServicePipe}.get(procedure)
41 | if not pipe_cls:
42 | raise RuntimeError("Emmett cannot handle the service you requested: %s" % procedure)
43 | return pipe_cls()
44 |
--------------------------------------------------------------------------------
/tests/test_utils.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | tests.utils
4 | -----------
5 |
6 | Test Emmett utils engine
7 | """
8 |
9 | from emmett.datastructures import sdict
10 | from emmett.utils import dict_to_sdict, is_valid_ip_address
11 |
12 |
13 | def test_is_valid_ip_address():
14 | result_localhost = is_valid_ip_address("127.0.0.1")
15 | assert result_localhost is True
16 |
17 | result_unknown = is_valid_ip_address("unknown")
18 | assert result_unknown is False
19 |
20 | result_ipv4_valid = is_valid_ip_address("::ffff:192.168.0.1")
21 | assert result_ipv4_valid is True
22 |
23 | result_ipv4_valid = is_valid_ip_address("192.168.256.1")
24 | assert result_ipv4_valid is False
25 |
26 | result_ipv6_valid = is_valid_ip_address("fd40:363d:ee85::")
27 | assert result_ipv6_valid is True
28 |
29 | result_ipv6_valid = is_valid_ip_address("fd40:363d:ee85::1::")
30 | assert result_ipv6_valid is False
31 |
32 |
33 | def test_dict_to_sdict():
34 | result_sdict = dict_to_sdict({"test": "dict"})
35 | assert isinstance(result_sdict, sdict)
36 | assert result_sdict.test == "dict"
37 |
38 | result_number = dict_to_sdict(1)
39 | assert not isinstance(result_number, sdict)
40 | assert result_number == 1
41 |
--------------------------------------------------------------------------------
/emmett/orm/migrations/utils.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | emmett.orm.migrations.utilities
4 | -------------------------------
5 |
6 | Provides some migration utilities.
7 |
8 | :copyright: 2014 Giovanni Barillari
9 | :license: BSD-3-Clause
10 | """
11 |
12 | from ..base import Database
13 | from .engine import Engine
14 | from .generation import Generator
15 | from .operations import MigrationOp, UpgradeOps
16 |
17 |
18 | class RuntimeGenerator(Generator):
19 | def _load_head_to_meta(self):
20 | pass
21 |
22 |
23 | class RuntimeMigration(MigrationOp):
24 | def __init__(self, engine: Engine, ops: UpgradeOps):
25 | super().__init__("runtime", ops, ops.reverse(), "runtime")
26 | self.engine = engine
27 | for op in self.upgrade_ops.ops:
28 | op.engine = self.engine
29 | for op in self.downgrade_ops.ops:
30 | op.engine = self.engine
31 |
32 | def up(self):
33 | for op in self.upgrade_ops.ops:
34 | op.run()
35 |
36 | def down(self):
37 | for op in self.downgrade_ops.ops:
38 | op.run()
39 |
40 |
41 | def generate_runtime_migration(db: Database) -> RuntimeMigration:
42 | engine = Engine(db)
43 | upgrade_ops = RuntimeGenerator.generate_from(db, None, None)
44 | return RuntimeMigration(engine, upgrade_ops)
45 |
--------------------------------------------------------------------------------
/emmett/ctx.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | emmett.ctx
4 | ----------
5 |
6 | Provides the current object.
7 | Used by application to deal with request related objects.
8 |
9 | :copyright: 2014 Giovanni Barillari
10 | :license: BSD-3-Clause
11 | """
12 |
13 | from datetime import datetime
14 |
15 | import pendulum
16 | from emmett_core.ctx import (
17 | Context as _Context,
18 | Current as _Current,
19 | RequestContext as _RequestContext,
20 | WSContext as _WsContext,
21 | _ctxv,
22 | )
23 | from emmett_core.utils import cachedprop
24 |
25 |
26 | class Context(_Context):
27 | __slots__ = []
28 |
29 | @property
30 | def now(self):
31 | return pendulum.instance(datetime.utcnow())
32 |
33 |
34 | class RequestContext(_RequestContext):
35 | __slots__ = []
36 |
37 | @cachedprop
38 | def language(self):
39 | return self.request.accept_language.best_match(list(self.app.translator._langmap))
40 |
41 |
42 | class WSContext(_WsContext):
43 | __slots__ = []
44 |
45 | @property
46 | def now(self):
47 | return pendulum.instance(datetime.utcnow())
48 |
49 | @cachedprop
50 | def language(self):
51 | return self.websocket.accept_language.best_match(list(self.app.translator._langmap))
52 |
53 |
54 | class Current(_Current):
55 | __slots__ = []
56 |
57 | def __init__(self):
58 | _ctxv.set(Context())
59 |
60 | @property
61 | def T(self):
62 | return self.ctx.app.translator
63 |
64 |
65 | current = Current()
66 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/examples/bloggy/tests.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | import pytest
4 |
5 | from emmett.orm.migrations.utils import generate_runtime_migration
6 | from bloggy import app, db, User, auth, setup_admin
7 |
8 |
9 | @pytest.fixture()
10 | def client():
11 | return app.test_client()
12 |
13 |
14 | @pytest.fixture(scope='module', autouse=True)
15 | def _prepare_db(request):
16 | with db.connection():
17 | migration = generate_runtime_migration(db)
18 | migration.up()
19 | setup_admin()
20 | yield
21 | with db.connection():
22 | User.all().delete()
23 | auth.delete_group('admin')
24 | migration.down()
25 |
26 |
27 | @pytest.fixture(scope='module')
28 | def logged_client():
29 | c = app.test_client()
30 | with c.get('/auth/login').context as ctx:
31 | c.post('/auth/login', data={
32 | 'email': 'doc@emmettbrown.com',
33 | 'password': 'fluxcapacitor',
34 | '_csrf_token': list(ctx.session._csrf)[-1]
35 | }, follow_redirects=True)
36 | return c
37 |
38 |
39 | def test_empty_db(client):
40 | r = client.get('/')
41 | assert 'No posts here so far' in r.data
42 |
43 |
44 | def test_login(logged_client):
45 | r = logged_client.get('/')
46 | assert r.context.session.auth.user is not None
47 |
48 |
49 | def test_no_admin_access(client):
50 | r = client.get('/new')
51 | assert r.context.response.status == 303
52 |
53 |
54 | def test_admin_access(logged_client):
55 | r = logged_client.get('/new')
56 | assert r.context.response.status == 200
57 |
--------------------------------------------------------------------------------
/emmett/orm/geo.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | emmett.orm.geo
4 | --------------
5 |
6 | Provides geographic facilities.
7 |
8 | :copyright: 2014 Giovanni Barillari
9 | :license: BSD-3-Clause
10 | """
11 |
12 | from .helpers import GeoFieldWrapper
13 |
14 |
15 | def Point(x, y):
16 | return GeoFieldWrapper("POINT(%f %f)" % (x, y))
17 |
18 |
19 | def Line(*coordinates):
20 | return GeoFieldWrapper("LINESTRING(%s)" % ",".join("%f %f" % point for point in coordinates))
21 |
22 |
23 | def Polygon(*coordinates_groups):
24 | try:
25 | if not isinstance(coordinates_groups[0][0], (tuple, list)):
26 | coordinates_groups = (coordinates_groups,)
27 | except Exception:
28 | pass
29 | return GeoFieldWrapper(
30 | "POLYGON(%s)"
31 | % (",".join(["(%s)" % ",".join("%f %f" % point for point in group) for group in coordinates_groups]))
32 | )
33 |
34 |
35 | def MultiPoint(*points):
36 | return GeoFieldWrapper("MULTIPOINT(%s)" % (",".join(["(%f %f)" % point for point in points])))
37 |
38 |
39 | def MultiLine(*lines):
40 | return GeoFieldWrapper(
41 | "MULTILINESTRING(%s)" % (",".join(["(%s)" % ",".join("%f %f" % point for point in line) for line in lines]))
42 | )
43 |
44 |
45 | def MultiPolygon(*polygons):
46 | return GeoFieldWrapper(
47 | "MULTIPOLYGON(%s)"
48 | % (
49 | ",".join(
50 | [
51 | "(%s)" % (",".join(["(%s)" % ",".join("%f %f" % point for point in group) for group in polygon]))
52 | for polygon in polygons
53 | ]
54 | )
55 | )
56 | )
57 |
--------------------------------------------------------------------------------
/emmett/assets/helpers.js:
--------------------------------------------------------------------------------
1 | /*
2 | helpers.js
3 | ----------
4 |
5 | Javascript helpers for the emmett project.
6 |
7 | :copyright: (c) 2014-2018 by Marco Stagni
8 | :license: BSD-3-Clause
9 | */
10 | (function($,undefined) {
11 |
12 | var emmett;
13 |
14 | $.emmett = emmett = {
15 | ajax: function (_url, params, to, _type) {
16 | /*simple ajax function*/
17 | query = '';
18 | if(typeof params == "string") {
19 | serialized = $(params).serialize();
20 | if(serialized) {
21 | query = serialized;
22 | }
23 | } else {
24 | pcs = [];
25 | if(params != null && params != undefined)
26 | for(i = 0; i < params.length; i++) {
27 | q = $("[name=" + params[i] + "]").serialize();
28 | if(q) {
29 | pcs.push(q);
30 | }
31 | }
32 | if(pcs.length > 0) {
33 | query = pcs.join("&");
34 | }
35 | }
36 | $.ajax({
37 | type: _type ? _type : "GET",
38 | url: _url,
39 | data: query,
40 | success: function (msg) {
41 | if(to) {
42 | if(to == ':eval') {
43 | eval(msg);
44 | }
45 | else if(typeof to == 'string') {
46 | $("#" + to).html(msg);
47 | }
48 | else {
49 | to(msg);
50 | }
51 | }
52 | }
53 | });
54 | },
55 |
56 | loadComponents : function() {
57 | $('[data-emt_remote]').each(function(index) {
58 | var f = function(obj) {
59 | var g = function(msg) {
60 | obj.html(msg);
61 | };
62 | return g;
63 | };
64 | $.emmett.ajax($(this).data("emt_remote"),[], f($(this)), "GET");
65 | });
66 | }
67 | }
68 |
69 | $(function() {
70 | emmett.loadComponents();
71 | });
72 |
73 | })($);
74 |
75 | ajax = $.emmett.ajax;
76 |
--------------------------------------------------------------------------------
/emmett/templating/lexers.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | emmett.templating.lexers
4 | ------------------------
5 |
6 | Provides the Emmett lexers for Renoir engine.
7 |
8 | :copyright: 2014 Giovanni Barillari
9 | :license: BSD-3-Clause
10 | """
11 |
12 | from renoir import Lexer
13 |
14 | from ..ctx import current
15 | from ..routing.urls import url
16 |
17 |
18 | class HelpersLexer(Lexer):
19 | helpers = [
20 | '',
21 | '',
22 | ]
23 |
24 | def process(self, ctx, value):
25 | for helper in self.helpers:
26 | ctx.html(helper.format(current.app._router_http._prefix_main))
27 |
28 |
29 | class MetaLexer(Lexer):
30 | def process(self, ctx, value):
31 | ctx.python_node("for name, value in current.response._meta_tmpl():")
32 | ctx.variable('\'
17 | by {{=comment.user.first_name}} on {{=comment.date}} 18 |