Various infrastructure updates (#168)

pull/170/head
Josh Karpel 1 year ago committed by GitHub
parent 57dcad28c4
commit f08ed7e7c7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -10,12 +10,11 @@ RUN : \
ENV COLORTERM=truecolor \ ENV COLORTERM=truecolor \
EDITOR=/usr/bin/vim EDITOR=/usr/bin/vim
WORKDIR /app RUN --mount=target=/spiel : \
&& pip install --no-cache-dir --disable-pip-version-check /spiel \
COPY . /app/spiel
RUN : \
&& pip install --no-cache-dir /app/spiel \
&& spiel version \ && spiel version \
&& : && :
WORKDIR /app
CMD ["spiel", "demo", "present"] CMD ["spiel", "demo", "present"]

@ -1,5 +1,12 @@
# Changelog # Changelog
## 0.4.3
### Fixed
- [#168](https://github.com/JoshKarpel/spiel/pull/168) The correct type for the `suspend` optional argument to slide-level keybinding functions is now available as `spiel.SuspendType`.
- [#168](https://github.com/JoshKarpel/spiel/pull/168) The Spiel Docker image no longer has a leftover copy of the `spiel` package directory inside the image unde `/app`.
## 0.4.2 ## 0.4.2
### Added ### Added

@ -0,0 +1,8 @@
# Gallery
- [pytest: It's What's For Testing](https://github.com/JoshKarpel/pytest-its-whats-for-testing) by [JoshKarpel](https://github.com/JoshKarpel).
### Submitting to the Gallery
If you've made a talk with Spiel, please feel free to submit a [pull request](https://github.com/JoshKarpel/spiel/pulls)
to add it to the gallery.

@ -3,6 +3,24 @@
Depending on your preferred workflow, Depending on your preferred workflow,
you can start a presentation in a variety of different ways. you can start a presentation in a variety of different ways.
!!! danger "Sandboxed Execution"
Spiel presentations are live Python code: they can do anything that Python can do.
You may want to run untrusted presentations (or even your own presentations) inside a container (but remember, even containers are not perfectly safe!).
We produce a [container image](https://github.com/users/JoshKarpel/packages/container/package/spiel)
that can be run by (for example) Docker.
Presentations without extra Python dependencies might just need to be bind-mounted into the container.
For example, if your demo file is at `$PWD/presentation/deck.py`, you could do
```bash
$ docker run -it --rm --mount type=bind,source=$PWD/presentation,target=/presentation ghcr.io/joshkarpel/spiel spiel present /presentation/deck.py
```
If the presentation has extra dependencies (like other Python packages),
we recommend building a new image that inherits our image (e.g., `FROM ghcr.io/joshkarpel/spiel:vX.Y.Z`).
Spiel's image itself inherits from the [Python base image](https://hub.docker.com/_/python).
## Using the `spiel` CLI ## Using the `spiel` CLI
!!! warning "Under Construction" !!! warning "Under Construction"
@ -16,9 +34,11 @@ The `present` function lets you start a presentation programmatically (i.e., fro
If your deck is defined in `talk/slides.py` like so: If your deck is defined in `talk/slides.py` like so:
```python title="talk/slides.py" ```python title="talk/slides.py"
#!/usr/bin/env python
from spiel import Deck, present from spiel import Deck, present
deck = Deck(name=f"pytest") deck = Deck(...)
... # construct your deck ... # construct your deck
@ -26,11 +46,16 @@ if __name__ == "__main__":
present(__file__) present(__file__)
``` ```
You can then present the deck by running You can then present the deck by running the script:
```console ```console
$ python talk/slides.py $ python talk/slides.py
``` ```
or Or by running the script as a module (you must have a `talk/__init__.py` file):
```console ```console
$ python -m talk.slides $ python -m talk.slides
``` ```
Or by running the script via its [shebang](https://en.wikipedia.org/wiki/Shebang_(Unix))
(after running `chmod +x talk/slides.py` to mark `talk/slides.py` as executable):
```console
$ talk/slides.py
```

@ -1,16 +0,0 @@
# Sandboxed Execution
Spiel presentations are live Python code: they can do anything that Python can do.
You may want to run untrusted presentations (or even your own presentations) inside a container (but remember, even containers are not perfectly safe!).
We produce a [container image](https://github.com/users/JoshKarpel/packages/container/package/spiel)
that can be run by (for example) Docker.
Presentations without extra Python dependencies might just need to be bind-mounted into the container.
For example, if your demo file is at `$PWD/presentation/deck.py`, you could do
```bash
$ docker run -it --rm --mount type=bind,source=$PWD/presentation,target=/presentation ghcr.io/joshkarpel/spiel spiel present /presentation/deck.py
```
If the presentation has extra dependencies (like other Python packages),
we recommend building a new image that inherits our image (e.g., `FROM ghcr.io/joshkarpel/spiel:vX.Y.Z`).
Spiel's image itself inherits from the [Python base image](https://hub.docker.com/_/python).

@ -72,6 +72,6 @@ extra:
nav: nav:
- Introduction: index.md - Introduction: index.md
- Sandboxed Execution: sandboxed-execution.md - presenting.md
- Presenting: presenting.md - gallery.md
- changelog.md - changelog.md

1730
poetry.lock generated

File diff suppressed because it is too large Load Diff

@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
[tool.poetry] [tool.poetry]
name = "spiel" name = "spiel"
version = "0.4.2" version = "0.4.3"
description = "A framework for building and presenting richly-styled presentations in your terminal using Python." description = "A framework for building and presenting richly-styled presentations in your terminal using Python."
readme="README.md" readme="README.md"
homepage="https://github.com/JoshKarpel/spiel" homepage="https://github.com/JoshKarpel/spiel"
@ -68,7 +68,6 @@ all = true
[tool.pytest.ini_options] [tool.pytest.ini_options]
addopts = ["--strict-markers", "--mypy", "-n", "auto"] addopts = ["--strict-markers", "--mypy", "-n", "auto"]
testpaths = ["tests", "spiel"] testpaths = ["tests", "spiel"]
norecursedirs = ["demo"]
[tool.mypy] [tool.mypy]
pretty = false pretty = false

@ -1,5 +1,7 @@
from spiel.app import present from spiel.app import SuspendType, present
from spiel.constants import __version__ from spiel.constants import __version__
from spiel.deck import Deck from spiel.deck import Deck
from spiel.slide import Slide from spiel.slide import Slide
from spiel.triggers import Triggers from spiel.triggers import Triggers
__all__ = ["present", "SuspendType", "__version__", "Deck", "Slide", "Triggers"]

@ -11,7 +11,7 @@ from contextlib import contextmanager, redirect_stderr, redirect_stdout
from functools import cached_property, partial from functools import cached_property, partial
from pathlib import Path from pathlib import Path
from time import monotonic from time import monotonic
from typing import Callable, Iterator, Optional from typing import Callable, ContextManager, Iterator, Optional
from rich.style import Style from rich.style import Style
from rich.text import Text from rich.text import Text
@ -60,6 +60,9 @@ def load_deck(path: Path) -> Deck:
return deck return deck
SuspendType = Callable[[], ContextManager[None]]
class SpielApp(App[None]): class SpielApp(App[None]):
CSS_PATH = "spiel.css" CSS_PATH = "spiel.css"
BINDINGS = [ BINDINGS = [

@ -23,7 +23,7 @@ class Deck:
def slide( def slide(
self, self,
title: str = "", title: str = "",
bindings: Mapping[str, Callable[[], None]] | None = None, bindings: Mapping[str, Callable[..., None]] | None = None,
) -> Callable[[Content], Slide]: ) -> Callable[[Content], Slide]:
def slideify(content: Content) -> Slide: def slideify(content: Content) -> Slide:
slide = Slide( slide = Slide(

@ -1,3 +1,5 @@
#!/usr/bin/env python
import inspect import inspect
import shutil import shutil
import socket import socket
@ -5,7 +7,6 @@ from datetime import datetime
from math import cos, floor, pi from math import cos, floor, pi
from pathlib import Path from pathlib import Path
from textwrap import dedent from textwrap import dedent
from typing import Callable, Iterable
from click import edit from click import edit
from rich.align import Align from rich.align import Align
@ -20,7 +21,7 @@ from rich.style import Style
from rich.syntax import Syntax from rich.syntax import Syntax
from rich.text import Text from rich.text import Text
from spiel import Slide, Triggers, __version__ from spiel import Slide, SuspendType, Triggers, __version__, present
from spiel.deck import Deck from spiel.deck import Deck
from spiel.renderables.image import Image from spiel.renderables.image import Image
@ -191,7 +192,7 @@ def dynamic() -> RenderableType:
Align.center( Align.center(
Panel( Panel(
Text.from_markup( Text.from_markup(
f"The local timezone on this computer ([bold]{socket.gethostname()}[/bold]) is [bold underline]{datetime.now().astimezone().tzinfo}[/bold underline]", f"The local timezone on this computer ([bold]{socket.gethostname()}[/bold]) is [bold]{datetime.now().astimezone().tzinfo}[/bold]",
style="bright_cyan", style="bright_cyan",
justify="center", justify="center",
) )
@ -303,9 +304,9 @@ def grid() -> RenderableType:
## Deck View ## Deck View
Try pressing `d` to go into "deck" view. Try pressing `d` to go into "deck" view.
You can still move between slides in deck view. You can move between slides in deck view using your arrow keys (right ``, left ``, up ``, and down ``).
Press `enter` to go back to "slide" view (this view), Press `enter` or `escape` to go back to "slide" view (this view),
on the currently-selected slide. on the currently-selected slide.
""" """
) )
@ -363,7 +364,7 @@ def watch() -> RenderableType:
) )
def edit_this_file(suspend: Callable[[], Iterable[None]]) -> None: def edit_this_file(suspend: SuspendType) -> None:
with suspend(): with suspend():
edit(filename=__file__) edit(filename=__file__)
@ -375,6 +376,7 @@ def edit_this_file(suspend: Callable[[], Iterable[None]]) -> None:
}, },
) )
def bindings() -> RenderableType: def bindings() -> RenderableType:
edit_function_src = dedent("".join(inspect.getsourcelines(edit_this_file)[0]))
return pad_markdown( return pad_markdown(
f"""\ f"""\
## Custom Per-Slide Key Bindings ## Custom Per-Slide Key Bindings
@ -383,12 +385,18 @@ def bindings() -> RenderableType:
which takes a mapping of key names to callables to call when that key is pressed. which takes a mapping of key names to callables to call when that key is pressed.
```python ```python
def edit_this_file(suspend: SuspendType) -> None:
with suspend():
edit(filename=__file__)
@deck.slide( @deck.slide(
title="Bindings", title="Bindings",
bindings={{ bindings={{
"e": edit_this_file, "e": edit_this_file,
}}, }},
) )
def bindings() -> RenderableType:
...
``` ```
If the callable takes an argument named `suspend`, If the callable takes an argument named `suspend`,
@ -421,3 +429,7 @@ def failure() -> RenderableType:
Deck reloading will still happen, so you can fix the error without stopping Spiel. Deck reloading will still happen, so you can fix the error without stopping Spiel.
""" """
) )
if __name__ == "__main__":
present(__file__)

@ -18,7 +18,7 @@ Content = Callable[..., RenderableType]
class Slide: class Slide:
title: str = "" title: str = ""
content: Content = lambda: Text() content: Content = lambda: Text()
bindings: Mapping[str, Callable[[], None]] = field(default_factory=dict) bindings: Mapping[str, Callable[..., None]] = field(default_factory=dict)
def render(self, triggers: Triggers) -> RenderableType: def render(self, triggers: Triggers) -> RenderableType:
signature = inspect.signature(self.content) signature = inspect.signature(self.content)

@ -9,8 +9,7 @@ from spiel.utils import clamp
def test_clamp(value_lower_upper: tuple[int, int, int]) -> None: def test_clamp(value_lower_upper: tuple[int, int, int]) -> None:
value, lower, upper = value_lower_upper value, lower, upper = value_lower_upper
clamped = clamp(value, lower, upper) clamped = clamp(value, lower, upper)
assert clamped <= upper assert lower <= clamped <= upper
assert clamped >= lower
@given(st.tuples(st.integers(), st.integers(), st.integers()).filter(lambda x: x[1] > x[2])) @given(st.tuples(st.integers(), st.integers(), st.integers()).filter(lambda x: x[1] > x[2]))

Loading…
Cancel
Save