• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

hasgeek / coaster / 9244418196

26 May 2024 03:39PM UTC coverage: 84.467% (-4.8%) from 89.263%
9244418196

push

github

web-flow
Add async support for Quart+Flask (#470)

This commit bumps the version number from 0.7 to 0.8 as it has extensive changes:

* Ruff replaces black, isort and flake8 for linting and formatting
* All decorators now support async functions and provide async wrapper implementations
* Some obsolete modules have been removed
* Pagination from Flask-SQLAlchemy is now included, removing that dependency (but still used in tests)
* New `compat` module provides wrappers to both Quart and Flake and is used by all other modules
* Some tests run using Quart. The vast majority of tests are not upgraded, nor are there tests for async decorators, so overall line coverage has dropped significantly. Comprehensive test coverage is still pending; for now we are using Funnel's tests as the extended test suite

648 of 1023 new or added lines in 29 files covered. (63.34%)

138 existing lines in 17 files now uncovered.

3948 of 4674 relevant lines covered (84.47%)

3.38 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

94.55
/src/coaster/sqlalchemy/markdown.py
1
"""Markdown composite columns."""
4✔
2

3
from __future__ import annotations
4✔
4

5
from typing import Any, Callable, Optional, Union
4✔
6

7
import sqlalchemy as sa
4✔
8
from markupsafe import Markup
4✔
9
from sqlalchemy.ext.mutable import MutableComposite
4✔
10
from sqlalchemy.orm import Composite, composite
4✔
11

12
from ..utils import markdown as markdown_processor
4✔
13

14
__all__ = ['MarkdownComposite', 'MarkdownColumn', 'markdown_column']
4✔
15

16

17
class MarkdownComposite(MutableComposite):
4✔
18
    """Represents Markdown text and rendered HTML as a composite column."""
4✔
19

20
    #: Markdown processor. Subclasses can override this. This has to be a staticmethod
21
    #: or the markdown processor will receive `self` as first parameter
22
    markdown = staticmethod(markdown_processor)
4✔
23
    #: Markdown options. Subclasses can override this
24
    options: Union[
4✔
25
        # Options may be a dictionary of string keys,
26
        dict[str, Any],
27
        # or a callable that returns such a dictionary
28
        Callable[[], dict[str, Any]],
29
    ] = {}
30

31
    def __init__(self, text: Optional[str], html: Optional[str] = None) -> None:
4✔
32
        """Create a composite."""
33
        if html is None:
4✔
34
            self.text = text  # This will regenerate HTML
4✔
35
        else:
36
            self._text = text
4✔
37
            self._html: Optional[str] = html
4✔
38

39
    # Return column values for SQLAlchemy to insert into the database
40
    def __composite_values__(
4✔
41
        self,
42
    ) -> tuple[Optional[str], Optional[str]]:
43
        """Return composite values."""
44
        return (self._text, self._html)
4✔
45

46
    # Return a string representation of the text (see class decorator)
47
    def __str__(self) -> str:
4✔
48
        """Return string representation."""
49
        return self.text or ''
4✔
50

51
    # Return a HTML representation of the text
52
    def __html__(self) -> str:
4✔
53
        """Return HTML representation."""
54
        return self._html or ''
4✔
55

56
    # Return a Markup string of the HTML
57
    @property
4✔
58
    def html(self) -> Optional[Markup]:
4✔
59
        """Return HTML as a property."""
60
        return Markup(self._html) if self._html is not None else None
4✔
61

62
    @property
4✔
63
    def text(self) -> Optional[str]:
4✔
64
        """Return text as a property."""
65
        return self._text
4✔
66

67
    @text.setter
4✔
68
    def text(self, value: Optional[str]) -> None:
4✔
69
        """Set the text value."""
70
        self._text = None if value is None else str(value)
4✔
71
        # Mypy and Pylance appear to be incorrectly typing self.markdown as taking
72
        # a parameter text=Literal[None] based on the first overload in the original
73
        # function declaration
74
        self._html = self.markdown(
4✔
75
            self._text,  # type: ignore[arg-type]
76
            **(
77
                self.options()  # pylint: disable=not-callable
78
                if callable(self.options)
79
                else self.options
80
            ),
81
        )
82
        self.changed()
4✔
83

84
    def __json__(self) -> Any:
4✔
85
        """Return JSON-compatible rendering of composite."""
86
        return {'text': self._text, 'html': self._html}
×
87

88
    # Compare text value
89
    def __eq__(self, other: object) -> bool:
4✔
90
        """Compare for equality."""
91
        if self is other:
4✔
NEW
92
            return True
×
93
        if isinstance(other, MarkdownComposite):
4✔
94
            return self.__composite_values__() == other.__composite_values__()
4✔
NEW
95
        return NotImplemented
×
96

97
    # Pickle support methods implemented as per SQLAlchemy documentation, but not
98
    # tested here as we don't use them.
99
    # https://docs.sqlalchemy.org/en/13/orm/extensions/mutable.html#id1
100

101
    def __getstate__(  # pragma: no cover
102
        self,
103
    ) -> tuple[Optional[str], Optional[str]]:
104
        """Get state for pickling."""
105
        # Return state for pickling
106
        return (self._text, self._html)
107

108
    def __setstate__(  # pragma: no cover
109
        self, state: tuple[Optional[str], Optional[str]]
110
    ) -> None:
111
        """Set state from pickle."""
112
        # Set state from pickle
113
        self._text, self._html = state
114
        self.changed()
115

116
    def __bool__(self) -> bool:
4✔
117
        """Return boolean value."""
118
        return bool(self._text)
4✔
119

120
    @classmethod
4✔
121
    def coerce(cls, _key: str, value: Any) -> MarkdownComposite:
4✔
122
        """Allow a composite column to be assigned a string value."""
123
        return cls(value)
4✔
124

125

126
def markdown_column(
4✔
127
    name: str,
128
    deferred: bool = False,
129
    group: Optional[str] = None,
130
    markdown: Optional[Callable] = None,
131
    options: Optional[dict] = None,
132
    **kwargs,
133
) -> Composite[MarkdownComposite]:
134
    """
135
    Create a composite column that autogenerates HTML from Markdown text.
136

137
    Creates two db columns named with ``_html`` and ``_text`` suffixes.
138

139
    :param str name: Column name base
140
    :param bool deferred: Whether the columns should be deferred by default
141
    :param str group: Defer column group
142
    :param markdown: Markdown processor function (default: Coaster's implementation)
143
    :param options: Additional options for the Markdown processor
144
    :param kwargs: Additional column options, passed to SQLAlchemy's column constructor
145
    """
146

147
    # Construct a custom subclass of MarkdownComposite and set the markdown processor
148
    # and processor options on it. We'll pass this class to SQLAlchemy's composite
149
    # constructor.
150
    class CustomMarkdownComposite(MarkdownComposite):
4✔
151
        """Customised markdown composite."""
4✔
152

153
    CustomMarkdownComposite.options = options if options is not None else {}
4✔
154
    CustomMarkdownComposite.markdown = staticmethod(
4✔
155
        markdown  # type:ignore[arg-type]
156
        if markdown is not None
157
        else markdown_processor
158
    )
159
    return composite(
4✔
160
        (
161
            CustomMarkdownComposite
162
            if (markdown is not None or options is not None)
163
            else MarkdownComposite
164
        ),
165
        sa.Column(name + '_text', sa.UnicodeText, **kwargs),
166
        sa.Column(name + '_html', sa.UnicodeText, **kwargs),
167
        deferred=deferred,
168
        group=group or name,
169
    )
170

171

172
# Compatibility name
173
MarkdownColumn = markdown_column
4✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc