pieui 0.1.0__py3-none-any.whl
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.
- pie/__init__.py +27 -0
- pie/__main__.py +99 -0
- pie/async_page.py +381 -0
- pie/card.py +218 -0
- pie/cli.py +244 -0
- pie/code/__init__.py +8 -0
- pie/code/build.py +26 -0
- pie/code/card_add.py +87 -0
- pie/code/create.py +64 -0
- pie/code/services/__init__.py +3 -0
- pie/code/services/models.py +13 -0
- pie/code/services/storage.py +379 -0
- pie/code/templates/pages/components/ajax_complex_component.py.j2 +14 -0
- pie/code/templates/pages/components/ajax_complex_container_component.py.j2 +14 -0
- pie/code/templates/pages/components/ajax_component.py.j2 +13 -0
- pie/code/templates/pages/components/ajax_container_component.py.j2 +14 -0
- pie/code/templates/pages/components/ajax_io_complex_component.py.j2 +19 -0
- pie/code/templates/pages/components/ajax_io_complex_container_component.py.j2 +19 -0
- pie/code/templates/pages/components/ajax_io_component.py.j2 +18 -0
- pie/code/templates/pages/components/ajax_io_container_component.py.j2 +18 -0
- pie/code/templates/pages/components/complex_component.py.j2 +9 -0
- pie/code/templates/pages/components/complex_container_component.py.j2 +10 -0
- pie/code/templates/pages/components/component.py.j2 +8 -0
- pie/code/templates/pages/components/container_component.py.j2 +10 -0
- pie/code/templates/pages/components/input_ajax_complex_component.py.j2 +17 -0
- pie/code/templates/pages/components/input_ajax_complex_container_component.py.j2 +17 -0
- pie/code/templates/pages/components/input_ajax_component.py.j2 +16 -0
- pie/code/templates/pages/components/input_ajax_io_complex_component.py.j2 +22 -0
- pie/code/templates/pages/components/input_ajax_io_complex_container_component.py.j2 +22 -0
- pie/code/templates/pages/components/input_ajax_io_component.py.j2 +21 -0
- pie/code/templates/pages/components/input_complex_component.py.j2 +9 -0
- pie/code/templates/pages/components/input_complex_container_component.py.j2 +10 -0
- pie/code/templates/pages/components/input_component.py.j2 +11 -0
- pie/code/templates/pages/components/input_io_complex_component.py.j2 +18 -0
- pie/code/templates/pages/components/input_io_complex_container_component.py.j2 +18 -0
- pie/code/templates/pages/components/input_io_component.py.j2 +17 -0
- pie/code/templates/pages/components/io_complex_component.py.j2 +15 -0
- pie/code/templates/pages/components/io_complex_container_component.py.j2 +15 -0
- pie/code/templates/pages/components/io_component.py.j2 +14 -0
- pie/code/templates/pages/components/io_container_component.py.j2 +15 -0
- pie/code/templates/pages/main.py.j2 +9 -0
- pie/code/templates/pages/page.py.j2 +0 -0
- pie/code/templates/web.py.j2 +26 -0
- pie/code/web.py +28 -0
- pie/components/__init__.py +0 -0
- pie/components/ajax_group.py +29 -0
- pie/components/hidden.py +40 -0
- pie/components/html_embed.py +70 -0
- pie/components/io_log.py +30 -0
- pie/components/one_of.py +28 -0
- pie/components/union.py +87 -0
- pie/config.py +24 -0
- pie/fastweb.py +1334 -0
- pie/hub/__init__.py +2 -0
- pie/hub/centrifuge_helpers/__init__.py +0 -0
- pie/hub/centrifuge_helpers/cent_client_wrapper.py +13 -0
- pie/hub/centrifuge_helpers/session_wrapper.py +69 -0
- pie/hub/checkpoint/__init__.py +12 -0
- pie/hub/checkpoint/archive.py +125 -0
- pie/hub/checkpoint/gdrive.py +42 -0
- pie/hub/checkpoint/mega_path.py +76 -0
- pie/hub/checkpoint/mega_url.py +16 -0
- pie/hub/checkpoint/path.py +16 -0
- pie/hub/checkpoint/pretrained_checkpoint.py +184 -0
- pie/hub/checkpoint/pretrained_jit_model.py +28 -0
- pie/hub/checkpoint/rclone.py +47 -0
- pie/hub/checkpoint/url.py +73 -0
- pie/hub/checkpoint/yadisk.py +12 -0
- pie/hub/svgpil/SVGImage.py +572 -0
- pie/hub/svgpil/__init__.py +0 -0
- pie/hub/svgpil/svg_stream.py +0 -0
- pie/hub/svgpil/test_draw.py +7 -0
- pie/types.py +83 -0
- pieui-0.1.0.dist-info/METADATA +230 -0
- pieui-0.1.0.dist-info/RECORD +77 -0
- pieui-0.1.0.dist-info/WHEEL +4 -0
- pieui-0.1.0.dist-info/entry_points.txt +3 -0
pie/__init__.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|
|
2
|
+
|
|
3
|
+
__author__ = "George K."
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
from .fastweb import Web
|
|
7
|
+
from .async_page import AsyncPage
|
|
8
|
+
from .card import Card, InputCard
|
|
9
|
+
from .components.hidden import HiddenCard
|
|
10
|
+
from .components.union import UnionCard
|
|
11
|
+
from .components.ajax_group import AjaxGroupCard
|
|
12
|
+
from .components.html_embed import HTMLEmbedCard
|
|
13
|
+
from .components.one_of import OneOfCard
|
|
14
|
+
from .components.io_log import IOLogCard
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
__all__ = ["__version__",
|
|
18
|
+
"Web",
|
|
19
|
+
"AsyncPage",
|
|
20
|
+
"InputCard",
|
|
21
|
+
"Card",
|
|
22
|
+
"HiddenCard",
|
|
23
|
+
"UnionCard",
|
|
24
|
+
"AjaxGroupCard",
|
|
25
|
+
"HTMLEmbedCard",
|
|
26
|
+
"OneOfCard",
|
|
27
|
+
"IOLogCard"]
|
pie/__main__.py
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"""PieDemo CLI
|
|
2
|
+
|
|
3
|
+
Command-line interface for the PieDemo framework.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
import sys
|
|
8
|
+
from typing import Optional
|
|
9
|
+
|
|
10
|
+
from .code import handle_card_add, handle_build, handle_create, handle_web, handle_web_verify
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
CARD_TYPES = {"simple", "complex", "container", "complex-container"}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
17
|
+
parser = argparse.ArgumentParser(
|
|
18
|
+
prog="pie",
|
|
19
|
+
description="Pie - Framework for building interactive web applications",
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
subparsers = parser.add_subparsers(dest="command", help="Available commands", required=True)
|
|
23
|
+
|
|
24
|
+
# piedemo web module:app [verify|build]
|
|
25
|
+
web_parser = subparsers.add_parser("web", help="Run or lint a web application")
|
|
26
|
+
web_parser.add_argument("web", help="Web in format module:attribute, e.g. some:app")
|
|
27
|
+
web_subparsers = web_parser.add_subparsers(dest="web_command")
|
|
28
|
+
web_subparsers.add_parser("verify", help="Verify the web application")
|
|
29
|
+
web_subparsers.add_parser("build", help="Build static JSON from a Web application")
|
|
30
|
+
|
|
31
|
+
card_parser = subparsers.add_parser("card", help="Manage card templates")
|
|
32
|
+
card_subparsers = card_parser.add_subparsers(dest="card_command", required=True)
|
|
33
|
+
card_add_parser = card_subparsers.add_parser("add", help="Create a card file from a template")
|
|
34
|
+
card_add_parser.add_argument("card_type", choices=sorted(CARD_TYPES))
|
|
35
|
+
card_add_parser.add_argument("card_name", metavar="NAME")
|
|
36
|
+
card_add_parser.add_argument("--io", action="store_true", dest="use_io")
|
|
37
|
+
card_add_parser.add_argument("--ajax", action="store_true", dest="use_ajax")
|
|
38
|
+
|
|
39
|
+
create_parser = subparsers.add_parser("create", help="Create a new Pie project")
|
|
40
|
+
create_parser.add_argument("project_name", metavar="NAME")
|
|
41
|
+
|
|
42
|
+
return parser
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
|
|
46
|
+
return build_parser().parse_args(argv)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def main(
|
|
50
|
+
command: str,
|
|
51
|
+
web: Optional[str] = None,
|
|
52
|
+
web_command: Optional[str] = None,
|
|
53
|
+
card_command: Optional[str] = None,
|
|
54
|
+
card_type: Optional[str] = None,
|
|
55
|
+
card_name: Optional[str] = None,
|
|
56
|
+
project_name: Optional[str] = None,
|
|
57
|
+
use_io: bool = False,
|
|
58
|
+
use_ajax: bool = False,
|
|
59
|
+
) -> int:
|
|
60
|
+
try:
|
|
61
|
+
if command == "web":
|
|
62
|
+
if web is None:
|
|
63
|
+
raise ValueError("Web path is required")
|
|
64
|
+
if web_command == "verify":
|
|
65
|
+
handle_web_verify(web)
|
|
66
|
+
elif web_command == "build":
|
|
67
|
+
handle_build(web)
|
|
68
|
+
else:
|
|
69
|
+
handle_web(web)
|
|
70
|
+
return 0
|
|
71
|
+
|
|
72
|
+
if command == "page":
|
|
73
|
+
return 0
|
|
74
|
+
if command == "card":
|
|
75
|
+
if card_command == "add":
|
|
76
|
+
handle_card_add(
|
|
77
|
+
card_name=card_name,
|
|
78
|
+
card_type=card_type,
|
|
79
|
+
use_io=use_io,
|
|
80
|
+
use_ajax=use_ajax,
|
|
81
|
+
)
|
|
82
|
+
return 0
|
|
83
|
+
if command == "create":
|
|
84
|
+
if project_name is None:
|
|
85
|
+
raise ValueError("Project name is required")
|
|
86
|
+
handle_create(project_name)
|
|
87
|
+
return 0
|
|
88
|
+
if command == "init":
|
|
89
|
+
return 0
|
|
90
|
+
if command == "login":
|
|
91
|
+
return 0
|
|
92
|
+
return 0
|
|
93
|
+
except ValueError as error:
|
|
94
|
+
print(error, file=sys.stderr)
|
|
95
|
+
return 1
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
if __name__ == "__main__":
|
|
99
|
+
raise SystemExit(main(**vars(parse_args())))
|
pie/async_page.py
ADDED
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import inspect
|
|
3
|
+
import json
|
|
4
|
+
import sys
|
|
5
|
+
import traceback
|
|
6
|
+
from typing import Any, AsyncGenerator, Callable, Dict, List, Literal, Optional, Union, TypeAlias
|
|
7
|
+
from fastapi.responses import StreamingResponse
|
|
8
|
+
from pie.card import Card
|
|
9
|
+
from pie.types import SocketIOEvent
|
|
10
|
+
|
|
11
|
+
try:
|
|
12
|
+
from fastapi_socketio import SocketManager
|
|
13
|
+
SocketManagerType: TypeAlias = SocketManager
|
|
14
|
+
except ImportError:
|
|
15
|
+
print(
|
|
16
|
+
"FastAPI socketio not installed. Emitting events is not available",
|
|
17
|
+
file=sys.stderr,
|
|
18
|
+
)
|
|
19
|
+
SocketManagerType: TypeAlias = Any
|
|
20
|
+
|
|
21
|
+
try:
|
|
22
|
+
from faststream.redis.fastapi import RedisBroker
|
|
23
|
+
from redis.asyncio import StrictRedis
|
|
24
|
+
|
|
25
|
+
RedisBrokerType: TypeAlias = RedisBroker
|
|
26
|
+
StrictRedisType: TypeAlias = StrictRedis
|
|
27
|
+
except ImportError:
|
|
28
|
+
print(
|
|
29
|
+
"FastStream & redis not installed. Cache and Background processing are not available",
|
|
30
|
+
file=sys.stderr,
|
|
31
|
+
)
|
|
32
|
+
RedisBrokerType: TypeAlias = Any
|
|
33
|
+
StrictRedisType: TypeAlias = Any
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class AsyncPage(object):
|
|
37
|
+
"""
|
|
38
|
+
Base class for asynchronous pages.
|
|
39
|
+
|
|
40
|
+
Provides methods for:
|
|
41
|
+
- filling and parsing data
|
|
42
|
+
- generating events
|
|
43
|
+
- SocketIO or Centrifuge integration
|
|
44
|
+
- caching via Redis
|
|
45
|
+
- AJAX registration
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
def __init__(self, is_typed: bool = False) -> None:
|
|
49
|
+
super(AsyncPage, self).__init__()
|
|
50
|
+
"""
|
|
51
|
+
Initialize AsyncPage with default attributes.
|
|
52
|
+
"""
|
|
53
|
+
self.is_typed: bool = is_typed
|
|
54
|
+
self.global_data: Dict[str, Any] = {}
|
|
55
|
+
self.ajax_post: Dict[str, Callable[..., Any]] = {}
|
|
56
|
+
self.ajax_get: Dict[str, Callable[..., Any]] = {}
|
|
57
|
+
self.io: Optional[SocketManagerType] = None
|
|
58
|
+
self.broker: Optional[RedisBrokerType] = None
|
|
59
|
+
self.cache: Optional[StrictRedisType] = None
|
|
60
|
+
|
|
61
|
+
def get_possible_fields(self) -> Dict[str, Card]:
|
|
62
|
+
return {
|
|
63
|
+
name: value
|
|
64
|
+
for name, value in inspect.getmembers(self)
|
|
65
|
+
if not name.startswith("__") and isinstance(value, Card)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
def get_io_cards(self) -> List[Card]:
|
|
69
|
+
"""
|
|
70
|
+
Return all cards that support IO events (SocketIO or Centrifuge).
|
|
71
|
+
|
|
72
|
+
Raises:
|
|
73
|
+
NotImplementedError: If the page has no fields.
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
List: IO-enabled cards.
|
|
77
|
+
"""
|
|
78
|
+
if hasattr(self, "fields"):
|
|
79
|
+
return self._get_io_cards(self.fields)
|
|
80
|
+
raise NotImplementedError("In " + self.__class__.__name__)
|
|
81
|
+
|
|
82
|
+
@staticmethod
|
|
83
|
+
def _get_io_cards(fields: Card) -> List[Card]:
|
|
84
|
+
"""
|
|
85
|
+
Helper to extract IO-enabled cards from a Card object.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
fields: Card object.
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
List: Cards supporting IO events.
|
|
92
|
+
"""
|
|
93
|
+
return [
|
|
94
|
+
card
|
|
95
|
+
for card in fields.children()
|
|
96
|
+
if hasattr(card, "name")
|
|
97
|
+
and (
|
|
98
|
+
(hasattr(card, "use_socketio_support") and card.use_socketio_support)
|
|
99
|
+
or (
|
|
100
|
+
hasattr(card, "use_centrifuge_support")
|
|
101
|
+
and card.use_centrifuge_support
|
|
102
|
+
)
|
|
103
|
+
)
|
|
104
|
+
]
|
|
105
|
+
|
|
106
|
+
async def get_content(self, **kwargs: Any):
|
|
107
|
+
"""
|
|
108
|
+
Return filled content of the page.
|
|
109
|
+
|
|
110
|
+
Raises:
|
|
111
|
+
NotImplementedError: If the page has no fields.
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
dict: Page content.
|
|
115
|
+
"""
|
|
116
|
+
if hasattr(self, "fields"):
|
|
117
|
+
return self.fill(self.fields, {})
|
|
118
|
+
raise NotImplementedError()
|
|
119
|
+
|
|
120
|
+
async def process(self, **data: Any):
|
|
121
|
+
"""
|
|
122
|
+
Process form submission.
|
|
123
|
+
|
|
124
|
+
Raises:
|
|
125
|
+
NotImplementedError
|
|
126
|
+
"""
|
|
127
|
+
raise NotImplementedError()
|
|
128
|
+
|
|
129
|
+
@staticmethod
|
|
130
|
+
def fill(
|
|
131
|
+
fields: Card,
|
|
132
|
+
data: Dict[str, Any],
|
|
133
|
+
inplace: bool = False,
|
|
134
|
+
generate: bool = True,
|
|
135
|
+
):
|
|
136
|
+
"""
|
|
137
|
+
Fill a card or V1 fields with data.
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
fields: Card or legacy fields.
|
|
141
|
+
data: Dictionary with input values.
|
|
142
|
+
inplace: Modify fields in place.
|
|
143
|
+
generate: Generate content after filling.
|
|
144
|
+
hook: Optional function to run after filling fields.
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
Generated content or modified fields.
|
|
148
|
+
"""
|
|
149
|
+
return fields.fill(data, inplace=inplace, generate=generate)
|
|
150
|
+
|
|
151
|
+
@staticmethod
|
|
152
|
+
def parse(fields: Card,
|
|
153
|
+
data: Dict[str, Any],
|
|
154
|
+
strict: bool = True) -> Dict[str, Any]:
|
|
155
|
+
"""
|
|
156
|
+
Parse input data into Card or legacy field structure.
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
fields: Card or legacy fields.
|
|
160
|
+
data: Input dictionary.
|
|
161
|
+
strict: Raise exceptions on invalid values.
|
|
162
|
+
|
|
163
|
+
Returns:
|
|
164
|
+
Parsed data dictionary.
|
|
165
|
+
"""
|
|
166
|
+
return fields.parse_all(data)
|
|
167
|
+
|
|
168
|
+
@staticmethod
|
|
169
|
+
def redirect_url(to: Optional[str], **kwargs: Any) -> Optional[str]:
|
|
170
|
+
"""
|
|
171
|
+
Build a redirect URL with optional query parameters.
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
to: Base URL.
|
|
175
|
+
kwargs: Query parameters.
|
|
176
|
+
|
|
177
|
+
Returns:
|
|
178
|
+
str: Full redirect URL.
|
|
179
|
+
"""
|
|
180
|
+
if to is None:
|
|
181
|
+
return None
|
|
182
|
+
if len(kwargs) == 0 or all([v is None for v in kwargs.values()]):
|
|
183
|
+
return to
|
|
184
|
+
params = "&".join([f"{k}={v}" for k, v in kwargs.items() if v is not None])
|
|
185
|
+
|
|
186
|
+
if "?" in to:
|
|
187
|
+
if to.endswith("&"):
|
|
188
|
+
return f"{to}{params}"
|
|
189
|
+
else:
|
|
190
|
+
return f"{to}&{params}"
|
|
191
|
+
else:
|
|
192
|
+
return f"{to}?{params}"
|
|
193
|
+
|
|
194
|
+
async def emit(
|
|
195
|
+
self,
|
|
196
|
+
event: Union[List[SocketIOEvent], SocketIOEvent],
|
|
197
|
+
to: Optional[str] = None,
|
|
198
|
+
) -> None:
|
|
199
|
+
"""
|
|
200
|
+
Emit SocketIO event(s) to connected clients.
|
|
201
|
+
|
|
202
|
+
Args:
|
|
203
|
+
event: Single or list of events.
|
|
204
|
+
to: Target recipient or broadcast.
|
|
205
|
+
"""
|
|
206
|
+
if self.io is None:
|
|
207
|
+
raise RuntimeError("SocketIO and Centrifuge not supported!")
|
|
208
|
+
|
|
209
|
+
if not isinstance(event, list):
|
|
210
|
+
events = [event]
|
|
211
|
+
else:
|
|
212
|
+
events = event
|
|
213
|
+
|
|
214
|
+
await asyncio.gather(*[self.io.emit(ev.name, ev.data, to=to)
|
|
215
|
+
for ev in events])
|
|
216
|
+
|
|
217
|
+
async def reload(self, to: Optional[str] = None) -> None:
|
|
218
|
+
"""
|
|
219
|
+
Trigger a page reload via SocketIO.
|
|
220
|
+
|
|
221
|
+
Args:
|
|
222
|
+
to: Target recipient or broadcast.
|
|
223
|
+
"""
|
|
224
|
+
if self.io is None:
|
|
225
|
+
raise RuntimeError("SocketIO not supported!")
|
|
226
|
+
print("Reload: ")
|
|
227
|
+
await self.io.emit("piereload", {}, to=to)
|
|
228
|
+
|
|
229
|
+
def register_ajax(
|
|
230
|
+
self,
|
|
231
|
+
pathname: str,
|
|
232
|
+
ajax_fn: Callable[..., Any],
|
|
233
|
+
method: Literal["GET", "POST"] = "POST",
|
|
234
|
+
) -> str:
|
|
235
|
+
"""
|
|
236
|
+
Register an AJAX endpoint.
|
|
237
|
+
|
|
238
|
+
Args:
|
|
239
|
+
pathname: URL path.
|
|
240
|
+
ajax_fn: Handler function.
|
|
241
|
+
method: HTTP method ("POST" or "GET").
|
|
242
|
+
|
|
243
|
+
Returns:
|
|
244
|
+
str: Pathname.
|
|
245
|
+
"""
|
|
246
|
+
if method == "POST":
|
|
247
|
+
self.ajax_post[pathname[1:]] = ajax_fn
|
|
248
|
+
elif method == "GET":
|
|
249
|
+
self.ajax_get[pathname[1:]] = ajax_fn
|
|
250
|
+
return pathname
|
|
251
|
+
|
|
252
|
+
@staticmethod
|
|
253
|
+
def create_ajax_event_streaming(
|
|
254
|
+
event_generator: AsyncGenerator[SocketIOEvent, None],
|
|
255
|
+
) -> StreamingResponse:
|
|
256
|
+
"""
|
|
257
|
+
Wrap async generator as a streaming response.
|
|
258
|
+
|
|
259
|
+
Args:
|
|
260
|
+
event_generator: Async generator of SocketIO events.
|
|
261
|
+
|
|
262
|
+
Returns:
|
|
263
|
+
StreamingResponse: Streaming events to the client.
|
|
264
|
+
"""
|
|
265
|
+
|
|
266
|
+
async def wrapped_generator() -> AsyncGenerator[str, None]:
|
|
267
|
+
try:
|
|
268
|
+
async for event in event_generator:
|
|
269
|
+
yield event.to_json() + "\n"
|
|
270
|
+
except asyncio.CancelledError:
|
|
271
|
+
print("Streaming cancelled by client", file=sys.stderr)
|
|
272
|
+
raise
|
|
273
|
+
except Exception:
|
|
274
|
+
traceback.print_exc(file=sys.stderr)
|
|
275
|
+
|
|
276
|
+
return StreamingResponse(wrapped_generator(), media_type="text/plain",
|
|
277
|
+
headers={
|
|
278
|
+
"Cache-Control": "no-cache",
|
|
279
|
+
"X-Accel-Buffering": "no",
|
|
280
|
+
})
|
|
281
|
+
|
|
282
|
+
def lock(self, name: str, timeout: int = 120) -> Any:
|
|
283
|
+
"""
|
|
284
|
+
Create a Redis lock.
|
|
285
|
+
|
|
286
|
+
Args:
|
|
287
|
+
name: Lock name.
|
|
288
|
+
timeout: Lock timeout in seconds.
|
|
289
|
+
|
|
290
|
+
Returns:
|
|
291
|
+
Redis lock object.
|
|
292
|
+
"""
|
|
293
|
+
if self.cache is None:
|
|
294
|
+
raise RuntimeError("Lock not supported!")
|
|
295
|
+
return self.cache.lock(f"lock:{name}", timeout=timeout)
|
|
296
|
+
|
|
297
|
+
async def page_lock(self, name: str, timeout: int = 120) -> Any:
|
|
298
|
+
"""
|
|
299
|
+
Create a page-scoped Redis lock.
|
|
300
|
+
|
|
301
|
+
Args:
|
|
302
|
+
name: Lock name.
|
|
303
|
+
timeout: Lock timeout.
|
|
304
|
+
|
|
305
|
+
Returns:
|
|
306
|
+
Redis lock object.
|
|
307
|
+
"""
|
|
308
|
+
return self.lock(f"{self.__class__.__name__}:{name}", timeout=timeout)
|
|
309
|
+
|
|
310
|
+
async def save_value(
|
|
311
|
+
self,
|
|
312
|
+
scope: str,
|
|
313
|
+
key: str,
|
|
314
|
+
value: Any,
|
|
315
|
+
encode: bool = False,
|
|
316
|
+
) -> Any:
|
|
317
|
+
"""
|
|
318
|
+
Save a string value to Redis.
|
|
319
|
+
|
|
320
|
+
Args:
|
|
321
|
+
scope: Key prefix.
|
|
322
|
+
key: Key name.
|
|
323
|
+
value: Value to save (must be str).
|
|
324
|
+
encode: JSON encode value.
|
|
325
|
+
|
|
326
|
+
Returns:
|
|
327
|
+
bool: Result of Redis set.
|
|
328
|
+
"""
|
|
329
|
+
if not isinstance(value, str):
|
|
330
|
+
raise TypeError("Value must be a string")
|
|
331
|
+
if scope in ["lock"]:
|
|
332
|
+
raise RuntimeError("scope must not be `lock`")
|
|
333
|
+
if ":" in scope:
|
|
334
|
+
raise RuntimeError("scope must not contain ':')")
|
|
335
|
+
if encode:
|
|
336
|
+
value = json.dumps(value)
|
|
337
|
+
return await self.cache.set(f"{scope}:{key}", value)
|
|
338
|
+
|
|
339
|
+
async def load_value(
|
|
340
|
+
self,
|
|
341
|
+
scope: str,
|
|
342
|
+
key: str,
|
|
343
|
+
decode: bool = False,
|
|
344
|
+
) -> Any:
|
|
345
|
+
"""
|
|
346
|
+
Load a value from Redis.
|
|
347
|
+
|
|
348
|
+
Args:
|
|
349
|
+
scope: Key prefix.
|
|
350
|
+
key: Key name.
|
|
351
|
+
decode: JSON decode value.
|
|
352
|
+
|
|
353
|
+
Returns:
|
|
354
|
+
str or decoded object.
|
|
355
|
+
"""
|
|
356
|
+
if scope in ["lock"]:
|
|
357
|
+
raise RuntimeError("scope must not be `lock`")
|
|
358
|
+
if ":" in scope:
|
|
359
|
+
raise RuntimeError("scope must not contain ':')")
|
|
360
|
+
value = await self.cache.get(f"{scope}:{key}")
|
|
361
|
+
if decode:
|
|
362
|
+
value = json.loads(value)
|
|
363
|
+
return value
|
|
364
|
+
|
|
365
|
+
def debug(self) -> None:
|
|
366
|
+
"""
|
|
367
|
+
Run a development server for debugging the page.
|
|
368
|
+
"""
|
|
369
|
+
from pie.fastweb import Web
|
|
370
|
+
|
|
371
|
+
web = Web(
|
|
372
|
+
{
|
|
373
|
+
"": self,
|
|
374
|
+
},
|
|
375
|
+
use_socketio_support=True,
|
|
376
|
+
enable_cors=True,
|
|
377
|
+
disable_serving=True,
|
|
378
|
+
serving_url="http://localhost:3000",
|
|
379
|
+
cors_origins=["http://localhost:8008", "http://localhost:3000"],
|
|
380
|
+
)
|
|
381
|
+
web.run(host="localhost", port=8008, debug=False)
|