stencila_types 2.0.0a25__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- stencila_types-2.0.0a25/PKG-INFO +22 -0
- stencila_types-2.0.0a25/README.md +1 -0
- stencila_types-2.0.0a25/pyproject.toml +110 -0
- stencila_types-2.0.0a25/src/stencila_types/__init__.py +0 -0
- stencila_types-2.0.0a25/src/stencila_types/shortcuts.py +360 -0
- stencila_types-2.0.0a25/src/stencila_types/types.py +3198 -0
- stencila_types-2.0.0a25/src/stencila_types/utilities.py +74 -0
- stencila_types-2.0.0a25/tests/.hypothesis/unicode_data/15.0.0/charmap.json.gz +0 -0
- stencila_types-2.0.0a25/tests/.hypothesis/unicode_data/15.0.0/codec-utf-8.json.gz +0 -0
- stencila_types-2.0.0a25/tests/__init__.py +0 -0
- stencila_types-2.0.0a25/tests/conftest.py +28 -0
- stencila_types-2.0.0a25/tests/test_shortcuts.py +113 -0
- stencila_types-2.0.0a25/tests/test_types.py +13 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: stencila_types
|
|
3
|
+
Version: 2.0.0a25
|
|
4
|
+
Summary: Python types for Stencila
|
|
5
|
+
Keywords: programmable reproducible interactive documents python SDK
|
|
6
|
+
Author-Email: Nokome Bentley <nokome@stencila.io>
|
|
7
|
+
License: Apache-2.0
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Intended Audience :: Science/Research
|
|
11
|
+
Classifier: Intended Audience :: Education
|
|
12
|
+
Classifier: Topic :: Software Development
|
|
13
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
14
|
+
Project-URL: Homepage, https://github.com/stencila/stencila/tree/main/python#readme
|
|
15
|
+
Project-URL: Repository, https://github.com/stencila/stencila
|
|
16
|
+
Requires-Python: >=3.10
|
|
17
|
+
Requires-Dist: cattrs>=23.2.3
|
|
18
|
+
Requires-Dist: beartype>=0.17.2
|
|
19
|
+
Requires-Dist: strenum>=0.4.15; python_version < "3.11"
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
|
|
22
|
+
# stencila_types
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# stencila_types
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "stencila_types"
|
|
3
|
+
version = "2.0.0-alpha.25"
|
|
4
|
+
description = "Python types for Stencila"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
authors = [
|
|
7
|
+
{ name = "Nokome Bentley", email = "nokome@stencila.io" },
|
|
8
|
+
]
|
|
9
|
+
dependencies = [
|
|
10
|
+
"cattrs>=23.2.3",
|
|
11
|
+
"beartype>=0.17.2",
|
|
12
|
+
"strenum>=0.4.15; python_version < \"3.11\"",
|
|
13
|
+
]
|
|
14
|
+
requires-python = ">=3.10"
|
|
15
|
+
keywords = [
|
|
16
|
+
"programmable",
|
|
17
|
+
"reproducible",
|
|
18
|
+
"interactive",
|
|
19
|
+
"documents",
|
|
20
|
+
"python",
|
|
21
|
+
"SDK",
|
|
22
|
+
]
|
|
23
|
+
classifiers = [
|
|
24
|
+
"Development Status :: 3 - Alpha",
|
|
25
|
+
"Intended Audience :: Developers",
|
|
26
|
+
"Intended Audience :: Science/Research",
|
|
27
|
+
"Intended Audience :: Education",
|
|
28
|
+
"Topic :: Software Development",
|
|
29
|
+
"License :: OSI Approved :: Apache Software License",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
[project.license]
|
|
33
|
+
text = "Apache-2.0"
|
|
34
|
+
|
|
35
|
+
[project.urls]
|
|
36
|
+
Homepage = "https://github.com/stencila/stencila/tree/main/python#readme"
|
|
37
|
+
Repository = "https://github.com/stencila/stencila"
|
|
38
|
+
|
|
39
|
+
[build-system]
|
|
40
|
+
requires = [
|
|
41
|
+
"pdm-backend",
|
|
42
|
+
]
|
|
43
|
+
build-backend = "pdm.backend"
|
|
44
|
+
|
|
45
|
+
[tool.pdm]
|
|
46
|
+
distribution = true
|
|
47
|
+
|
|
48
|
+
[tool.pdm.dev-dependencies]
|
|
49
|
+
testing = [
|
|
50
|
+
"pytest>=8.1.1",
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
[tool.pytest.ini_options]
|
|
54
|
+
|
|
55
|
+
[tool.ruff]
|
|
56
|
+
target-version = "py310"
|
|
57
|
+
line-length = 88
|
|
58
|
+
src = [
|
|
59
|
+
"src",
|
|
60
|
+
"tests",
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
[tool.ruff.lint]
|
|
64
|
+
select = [
|
|
65
|
+
"F",
|
|
66
|
+
"E",
|
|
67
|
+
"W",
|
|
68
|
+
"C90",
|
|
69
|
+
"N",
|
|
70
|
+
"UP",
|
|
71
|
+
"S",
|
|
72
|
+
"B",
|
|
73
|
+
"A",
|
|
74
|
+
"C4",
|
|
75
|
+
"PIE",
|
|
76
|
+
"ISC",
|
|
77
|
+
"T20",
|
|
78
|
+
"PT",
|
|
79
|
+
"RET",
|
|
80
|
+
"SIM",
|
|
81
|
+
"PTH",
|
|
82
|
+
"Q",
|
|
83
|
+
"PLR",
|
|
84
|
+
"PLE",
|
|
85
|
+
"I001",
|
|
86
|
+
]
|
|
87
|
+
ignore = [
|
|
88
|
+
"N812",
|
|
89
|
+
]
|
|
90
|
+
unfixable = [
|
|
91
|
+
"T20",
|
|
92
|
+
"F841",
|
|
93
|
+
]
|
|
94
|
+
|
|
95
|
+
[tool.ruff.lint.per-file-ignores]
|
|
96
|
+
"src/stencila_types/types.py" = [
|
|
97
|
+
"E501",
|
|
98
|
+
]
|
|
99
|
+
"tests/**" = [
|
|
100
|
+
"S101",
|
|
101
|
+
]
|
|
102
|
+
|
|
103
|
+
[tool.pyright]
|
|
104
|
+
include = [
|
|
105
|
+
"src",
|
|
106
|
+
"tests",
|
|
107
|
+
]
|
|
108
|
+
pythonVersion = "3.10"
|
|
109
|
+
venvPath = "."
|
|
110
|
+
venv = ".venv"
|
|
File without changes
|
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
from collections.abc import Iterable
|
|
2
|
+
from typing import Optional, TypeAlias, Union
|
|
3
|
+
|
|
4
|
+
from beartype import beartype
|
|
5
|
+
|
|
6
|
+
from stencila_types import types as T # noqa: N812
|
|
7
|
+
|
|
8
|
+
# Our convertible type handles anything that is iterable (lists, tuples,
|
|
9
|
+
# generators etc.)
|
|
10
|
+
|
|
11
|
+
ConvertibleToInline: TypeAlias = str | T.Inline | Iterable[T.Inline | str]
|
|
12
|
+
ConvertibleToBlocks: TypeAlias = str | T.Block | Iterable[T.Block | str]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def flatten(args):
|
|
16
|
+
"""This makes our job much easier by normalising everything to an iterable."""
|
|
17
|
+
if isinstance(args, str) or not isinstance(args, Iterable):
|
|
18
|
+
yield args
|
|
19
|
+
else:
|
|
20
|
+
for elem in args:
|
|
21
|
+
yield from flatten(elem)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@beartype
|
|
25
|
+
def convert_to_inlines(args: ConvertibleToInline):
|
|
26
|
+
return [T.Text(value=arg) if isinstance(arg, str) else arg for arg in flatten(args)]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@beartype
|
|
30
|
+
def convert_to_blocks(args: ConvertibleToBlocks):
|
|
31
|
+
return [
|
|
32
|
+
T.Paragraph(content=[T.Text(value=arg)]) if isinstance(arg, str) else arg
|
|
33
|
+
for arg in flatten(args)
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@beartype
|
|
38
|
+
def aud(url: str) -> T.AudioObject:
|
|
39
|
+
return T.AudioObject(content_url=url)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
# Force keywords, otherwise it is ambiguous
|
|
43
|
+
@beartype
|
|
44
|
+
def btn(*, name: str, code: str) -> T.Button:
|
|
45
|
+
return T.Button(name=name, code=code)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@beartype
|
|
49
|
+
def ct(target: str):
|
|
50
|
+
return T.Cite(target=target, citation_mode=T.CitationMode.Parenthetical)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@beartype
|
|
54
|
+
def ctg(targets: Iterable[str]):
|
|
55
|
+
return T.CiteGroup(items=[ct(t) for t in targets])
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@beartype
|
|
59
|
+
def ce(code: str, *, lang: str | None = None) -> T.CodeExpression:
|
|
60
|
+
return T.CodeExpression(code=code, programming_language=lang)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@beartype
|
|
64
|
+
def ci(code: str):
|
|
65
|
+
return T.CodeInline(code=code)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@beartype
|
|
69
|
+
def dei(content: ConvertibleToInline) -> T.DeleteInline:
|
|
70
|
+
return T.DeleteInline(content=convert_to_inlines(content))
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@beartype
|
|
74
|
+
def em(content: str | T.Text) -> T.Emphasis:
|
|
75
|
+
# Emphasis actually takes a list, but if these are shortcuts...
|
|
76
|
+
if isinstance(content, str):
|
|
77
|
+
content = T.Text(value=content)
|
|
78
|
+
return T.Emphasis(content=[content])
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@beartype
|
|
82
|
+
def img(url: str) -> T.ImageObject:
|
|
83
|
+
return T.ImageObject(content_url=url)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@beartype
|
|
87
|
+
def isi(content: ConvertibleToInline) -> T.InsertInline:
|
|
88
|
+
return T.InsertInline(content=convert_to_inlines(content))
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@beartype
|
|
92
|
+
def lnk(content: ConvertibleToInline, target: str) -> T.Link:
|
|
93
|
+
return T.Link(content=convert_to_inlines(content), target=target)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@beartype
|
|
97
|
+
def mi(code: str, *, lang: str | None = None) -> T.MathInline:
|
|
98
|
+
return T.MathInline(code=code, math_language=lang)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@beartype
|
|
102
|
+
def nte(note_type: T.NoteType, content: ConvertibleToBlocks) -> T.Note:
|
|
103
|
+
return T.Note(note_type=note_type, content=convert_to_blocks(content))
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@beartype
|
|
107
|
+
def par(name: str) -> T.Parameter:
|
|
108
|
+
return T.Parameter(name=name)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@beartype
|
|
112
|
+
def rei(
|
|
113
|
+
content: ConvertibleToInline, replacement: ConvertibleToInline
|
|
114
|
+
) -> T.ReplaceInline:
|
|
115
|
+
return T.ReplaceInline(
|
|
116
|
+
content=convert_to_inlines(content),
|
|
117
|
+
replacement=convert_to_inlines(replacement),
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
@beartype
|
|
122
|
+
def qi(content: ConvertibleToInline) -> T.QuoteInline:
|
|
123
|
+
return T.QuoteInline(content=convert_to_inlines(content))
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
@beartype
|
|
127
|
+
def sti(code: str, content: ConvertibleToInline) -> T.StyledInline:
|
|
128
|
+
return T.StyledInline(code=code, content=convert_to_inlines(content))
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
@beartype
|
|
132
|
+
def stk(content: ConvertibleToInline) -> T.Strikeout:
|
|
133
|
+
return T.Strikeout(content=convert_to_inlines(content))
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
@beartype
|
|
137
|
+
def stg(content: ConvertibleToInline) -> T.Strong:
|
|
138
|
+
return T.Strong(content=convert_to_inlines(content))
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
@beartype
|
|
142
|
+
def sub(content: ConvertibleToInline) -> T.Subscript:
|
|
143
|
+
return T.Subscript(content=convert_to_inlines(content))
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
@beartype
|
|
147
|
+
def sup(content: ConvertibleToInline) -> T.Superscript:
|
|
148
|
+
return T.Superscript(content=convert_to_inlines(content))
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
@beartype
|
|
152
|
+
def t(content: str) -> T.Text:
|
|
153
|
+
return T.Text(value=str(content))
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
@beartype
|
|
157
|
+
def u(content: ConvertibleToInline) -> T.Underline:
|
|
158
|
+
return T.Underline(content=convert_to_inlines(content))
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
@beartype
|
|
162
|
+
def adm(
|
|
163
|
+
admonition_type: T.AdmonitionType,
|
|
164
|
+
content: ConvertibleToBlocks,
|
|
165
|
+
*,
|
|
166
|
+
title: str | None = None,
|
|
167
|
+
) -> T.Admonition:
|
|
168
|
+
return T.Admonition(
|
|
169
|
+
admonition_type=admonition_type,
|
|
170
|
+
content=convert_to_blocks(content),
|
|
171
|
+
title=convert_to_inlines(title),
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
@beartype
|
|
176
|
+
def clb(source: str, args: list[T.CallArgument]) -> T.CallBlock:
|
|
177
|
+
return T.CallBlock(source=source, arguments=args)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
@beartype
|
|
181
|
+
def arg(name: str, code: str) -> T.CallArgument:
|
|
182
|
+
return T.CallArgument(name=name, code=code)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
@beartype
|
|
186
|
+
def clm(claim_type: T.ClaimType, content: ConvertibleToBlocks) -> T.Claim:
|
|
187
|
+
return T.Claim(
|
|
188
|
+
claim_type=claim_type,
|
|
189
|
+
content=convert_to_blocks(content),
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
@beartype
|
|
194
|
+
def cb(code: str, *, lang: str | None = None) -> T.CodeBlock:
|
|
195
|
+
return T.CodeBlock(code=code, programming_language=lang)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
@beartype
|
|
199
|
+
def cc(code: str, *, lang: str | None = None) -> T.CodeChunk:
|
|
200
|
+
return T.CodeChunk(code=code, programming_language=lang)
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
@beartype
|
|
204
|
+
def deb(content: ConvertibleToBlocks) -> T.DeleteBlock:
|
|
205
|
+
return T.DeleteBlock(content=convert_to_blocks(content))
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
@beartype
|
|
209
|
+
def fig(content: ConvertibleToBlocks) -> T.Figure:
|
|
210
|
+
return T.Figure(content=convert_to_blocks(content))
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
@beartype
|
|
214
|
+
def frb(code: str, variable: str, content: ConvertibleToBlocks) -> T.ForBlock:
|
|
215
|
+
return T.ForBlock(code=code, variable=variable, content=convert_to_blocks(content))
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
@beartype
|
|
219
|
+
def h(level: int, content: ConvertibleToInline):
|
|
220
|
+
return T.Heading(level=level, content=convert_to_inlines(content))
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
@beartype
|
|
224
|
+
def h1(content: ConvertibleToInline) -> T.Heading:
|
|
225
|
+
return h(1, content)
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
@beartype
|
|
229
|
+
def h2(content: ConvertibleToInline) -> T.Heading:
|
|
230
|
+
return h(2, content)
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
@beartype
|
|
234
|
+
def h3(content: ConvertibleToInline) -> T.Heading:
|
|
235
|
+
return h(3, content)
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
@beartype
|
|
239
|
+
def h4(content: ConvertibleToInline) -> T.Heading:
|
|
240
|
+
return h(4, content)
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
@beartype
|
|
244
|
+
def h5(content: ConvertibleToInline) -> T.Heading:
|
|
245
|
+
return h(5, content)
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
@beartype
|
|
249
|
+
def h6(content: ConvertibleToInline) -> T.Heading:
|
|
250
|
+
return h(6, content)
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
@beartype
|
|
254
|
+
def ifb(clauses: list[T.IfBlockClause]) -> T.IfBlock:
|
|
255
|
+
return T.IfBlock(clauses=clauses)
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
@beartype
|
|
259
|
+
def ibc(
|
|
260
|
+
code: str, content: ConvertibleToBlocks, *, lang: str | None = None
|
|
261
|
+
) -> T.IfBlockClause:
|
|
262
|
+
return T.IfBlockClause(
|
|
263
|
+
code=code, programming_language=lang, content=convert_to_blocks(content)
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
@beartype
|
|
268
|
+
def inb(source: str) -> T.IncludeBlock:
|
|
269
|
+
return T.IncludeBlock(source=source)
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
@beartype
|
|
273
|
+
def isb(content: ConvertibleToBlocks) -> T.InsertBlock:
|
|
274
|
+
return T.InsertBlock(content=convert_to_blocks(content))
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
@beartype
|
|
278
|
+
def ol(items: list[T.ListItem]) -> T.List:
|
|
279
|
+
return T.List(items=items, order=T.ListOrder.Ascending)
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
@beartype
|
|
283
|
+
def ul(items: list[T.ListItem]) -> T.List:
|
|
284
|
+
return T.List(items=items, order=T.ListOrder.Unordered)
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
@beartype
|
|
288
|
+
def li(content: ConvertibleToBlocks) -> T.ListItem:
|
|
289
|
+
return T.ListItem(content=convert_to_blocks(content))
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
@beartype
|
|
293
|
+
def mb(code: str, *, lang: str | None = None) -> T.MathBlock:
|
|
294
|
+
return T.MathBlock(code=code, math_language=lang)
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
@beartype
|
|
298
|
+
def p(*args: ConvertibleToInline) -> T.Paragraph:
|
|
299
|
+
return T.Paragraph(content=convert_to_inlines(args))
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
@beartype
|
|
303
|
+
def qb(content: ConvertibleToBlocks) -> T.QuoteBlock:
|
|
304
|
+
return T.QuoteBlock(content=convert_to_blocks(content))
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
@beartype
|
|
308
|
+
def reb(
|
|
309
|
+
content: ConvertibleToBlocks, replacement: ConvertibleToBlocks
|
|
310
|
+
) -> T.ReplaceBlock:
|
|
311
|
+
return T.ReplaceBlock(
|
|
312
|
+
content=convert_to_blocks(content),
|
|
313
|
+
replacement=convert_to_blocks(replacement),
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
@beartype
|
|
318
|
+
def sec(content: ConvertibleToBlocks) -> T.Section:
|
|
319
|
+
return T.Section(content=convert_to_blocks(content))
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
@beartype
|
|
323
|
+
def stb(style: str, content: ConvertibleToBlocks) -> T.StyledBlock:
|
|
324
|
+
return T.StyledBlock(code=style, content=convert_to_blocks(content))
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
@beartype
|
|
328
|
+
def tbl(rows: list[T.TableRow]) -> T.Table:
|
|
329
|
+
return T.Table(rows=rows)
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
@beartype
|
|
333
|
+
def tr(cells: list[T.TableCell]) -> T.TableRow:
|
|
334
|
+
return T.TableRow(cells=cells)
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
@beartype
|
|
338
|
+
def th(content: ConvertibleToBlocks) -> T.TableCell:
|
|
339
|
+
return T.TableCell(
|
|
340
|
+
content=convert_to_blocks(content),
|
|
341
|
+
cell_type=T.TableCellType.HeaderCell,
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
@beartype
|
|
346
|
+
def td(content: ConvertibleToBlocks) -> T.TableCell:
|
|
347
|
+
return T.TableCell(
|
|
348
|
+
content=convert_to_blocks(content),
|
|
349
|
+
cell_type=T.TableCellType.DataCell,
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
@beartype
|
|
354
|
+
def tb() -> T.ThematicBreak:
|
|
355
|
+
return T.ThematicBreak()
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
@beartype
|
|
359
|
+
def art(content: ConvertibleToBlocks) -> T.Article:
|
|
360
|
+
return T.Article(content=convert_to_blocks(content))
|