domingo, 14 de agosto de 2022

Esta fue una presentación sobre screenpy una libreria que usa el patron screenplay en python y permite su uso Ver la documentación

A continuación vemos el notebook alojado como gist

Open In Colab

ScreenPy

Es una implementación del patrón screenplay en python

En este notebook es una traducción del ejemplo completo de screenplay https://screenpy-docs.readthedocs.io/en/latest/example.html

image.png

%pip install screenpy
11692.70s - pydevd: Sending message related to process being replaced timed-out after 5 seconds


Requirement already satisfied: screenpy in /home/scot3004/proyectos/comunidades/screenpy_examples/venv/lib/python3.8/site-packages (4.0.1)
Requirement already satisfied: typing-extensions<4.2,>=4.1.1 in /home/scot3004/proyectos/comunidades/screenpy_examples/venv/lib/python3.8/site-packages (from screenpy) (4.1.1)
Requirement already satisfied: PyHamcrest<2.1,>=2.0.0 in /home/scot3004/proyectos/comunidades/screenpy_examples/venv/lib/python3.8/site-packages (from screenpy) (2.0.3)
WARNING: You are using pip version 19.2.3, however version 22.2.1 is available.
You should consider upgrading via the 'pip install --upgrade pip' command.
Note: you may need to restart the kernel to use updated packages.

Actores (Actor)

Los actores representan a sus usuarios finales. Se les otorgarán Habilidades, realizarán Acciones, harán Preguntas y (con suerte) cumplirán Resoluciones durante el transcurso de sus pruebas.

Instanciar un Actor es simple:

from screenpy import AnActor

Cameron = AnActor.named("Cameron")
Polly = AnActor.named("Polly")

El nombre que le dé a sus Actores se utilizará para registrar las Acciones que realizan. Para realizar Acciones más interesantes, su Actor necesitará algunas Habilidades:

# grant abilities on instantiation
Cameron = AnActor.named("Cameron").who_can(ControlCameras())

# or later, if you want
Polly.can(PollTheAudience())

De nuestro ejemplo completo, otorgamos a Cameron la capacidad de controlar cámaras y a Polly la capacidad de sondear a la audiencia. Estas Habilidades permitieron a nuestros Actores realizar varias Acciones.

Habilidades (Abilities)

Las habilidades habilitan a los Actores a usar librerias, conectarse con recursos, o bien cualquier otra cosa

En nuestro ejemplo completo, otorgamos a Cameron la capacidad de controlar cámaras. Esta habilidad utiliza una biblioteca inventada, cam_py, para controlar las cámaras. Esta habilidad podría verse así:


import cam_py


class ControlCameras:
    """Enable an Actor to control cameras through cam_py.

    Examples::

        the_actor.can(ControlCameras())
    """

    def __init__(self) -> None:
        self.campy_session = cam_py.RecordingSession()
        self.cameras = []

    def forget(self) -> None:
        for camera in self.cameras:
            camera.stop()
        self.campy_session.wrap()

ControlCameras
__main__.ControlCameras

Las habilidades son olvidables, lo que significa que una habilidad debe tener un método forget. Este método se encarga de limpiar los cabos sueltos que cuelgan.

Tanto las Acciones como las Preguntas pueden usar las Habilidades de un Actor.

Acciones (Actions)

Durante una prueba, tus Actores realizarán varias Acciones. Los actores realizan acciones para configurar y realizar la prueba.

En nuestro ejemplo completo, Cameron usa varias acciones. Uno de ellos es StartRecording.

Así es como se podría codificar esa acción:

# %load actions/start_recording.py
"""
Start recording a screenplay on one or more cameras!
"""

import cam_py
from screenpy import Actor
from screenpy.pacing import beat

from ..abilities import ControlCameras


class StartRecording:
    """Starts recording on one or more cameras.

    Examples::

        the_actor.attempts_to(StartRecording())

        camera = Camera("Character")
        the_actor.attempts_to(StartRecording.on(camera))

        camera1 = Camera("Character1")
        camera2 = Camera("Character2")
        the_actor.attempts_to(StartRecording.on(camera1).and_(camera2))
    """

    def on(self, camera: cam_py.Camera) -> "StartRecording":
        """Record on an already-created camera."""
        self.cameras.append(camera)
        return self

    and_ = on

    @beat("{} starts recording on {cameras_to_log}.")
    def perform_as(self, the_actor: Actor) -> None:
        """Direct the actor to start recording on their cameras."""
        if not self.cameras:
            self.cameras = [cam_py.Camera("Main")]

        campy_session = the_actor.ability_to(ControlCameras).campy_session
        for camera in self.cameras:
            camera.record(self.script)
            campy_session.add_camera(camera)

    @property
    def cameras_to_log(self) -> str:
        """Get a nice list of all the cameras for the logged beat."""
        return ", ".join(camera.character for camera in self.cameras)

    def __init__(self, script: str) -> None:
        self.script = script
        self.cameras = []

Tareas (Tasks)

Las tareas son una agrupación de acciones. tienen un método perform_as ver clase Performable, al igual que las acciones, lo que significa que tienen un método perform_as. Ese es el único requisito.

Puede crear tareas para un grupo repetido de acciones, como LogIn para iniciar sesión. También puede crear tareas para describir un grupo de acciones que solo realiza una vez con un nombre más descriptivo, como ChangeProfilePicture.

Se utilizaron dos tareas en nuestro ejemplo completo: CutToCloseUp y DollyZoom. Veamos cómo podría implementarse DollyZoom:

# %load tasks/dolly_zoom.py
"""
Dolly-zoom, that classic tension shot.
https://en.wikipedia.org/wiki/Dolly_zoom
"""

from typing import Optional

from screenpy import Actor
from screenpy.pacing import beat

from ..abilities import ControlCameras
from ..actions import Dolly, Simultaneously, Zoom


class DollyZoom:
    """Perform a dolly zoom (optionally on a character) to enhance drama.

    Examples::

        the_actor.attempts_to(DollyZoom())

        the_actor.attempts_to(DollyZoom.on("Alfred Hitchcock"))
    """

    @staticmethod
    def on(character: str) -> "DollyZoom":
        """Specify the character to put in frame before dolly zooming."""
        return DollyZoom(character)

    @beat("{} executes a thrilling dolly zoom{detail}!")
    def perform_as(self, the_actor: Actor) -> None:
        """Direct the actor to dolly zoom on their camera."""
        if self.character:
            campy_session = the_actor.ability_to(ControlCameras).campy_session
            camera = campy_session.get_camera_on_character(self.character)
            zoom = Zoom.in_().on_camera(camera)
        else:
            zoom = Zoom.in_()

        the_actor.attempts_to(
            Simultaneously(
                Dolly().backward(),
                zoom,
            ),
        )

    def __init__(self, character: Optional[str] = None) -> None:
        self.character = character
        self.detail = f" on {character}" if character else ""

Como puede ver, esta Tarea simplemente realiza otras tres Acciones. Simultaneously, es una acción en cam_py que realiza todas las acciones dadas a la vez; Dolly, que mueve la cámara en la dirección especificada; Zoom, que acerca o aleja la cámara.

El Narrador leerá las líneas beat() para cada acción. La línea de tareas de DollyZoom aparecerá para encapsular las líneas de otras acciones. Así es como se ve desde el StdOutAdapter incorporado:

INFO     screenpy:stdout_adapter.py:42 Cameron executes a dramatic dolly zoom!
INFO     screenpy:stdout_adapter.py:42     Cameron performs some thrilling camerawork simultaneously!
INFO     screenpy:stdout_adapter.py:42         Cameron dollies the active camera backward.
INFO     screenpy:stdout_adapter.py:42         Cameron zooms in.

Preguntas (Questions)

Cuando esté listo para hacer una verificación (assert) en su prueba, su actor puede hacer una pregunta. La respuesta a esta Pregunta se comparara con una Resolución. Este emparejamiento forma una afirmación en ScreenPy.

Nuestro ejemplo completo usa dos preguntas: AudienceTension y TopAudienceReaction. Veamos cómo se vería esto último:

# %load questions/top_audience_reaction.py
"""
Gather information about the audience's tension.
"""

from screenpy import Actor

from ..abilities import PollTheAudience


class TopAudienceReaction:
    """Ask about the audience's most popular reaction.

    Examples::

        the_actor.should(See.the(TopAudienceReaction(), Equals(LAUGHING))
    """

    def answered_by(self, the_actor: Actor) -> str:
        """Direct the actor to ask about the audience's top mood."""
        pollster = the_actor.ability_to(PollTheAudience).poll_connection
        return pollster.poll_mood().top_mood

Una pregunta es Answerable, quiere decir que tiene un método answered_by.

Pasar una pregunta junto con una resolución a la acción See es cómo hacer afirmaciones en ScreenPy. La Pregunta proporciona el valor real mientras que la Resolución proporciona el valor esperado.

Resoluciones (Resolutions)

Las resoluciones proporcionan tanto el valor esperado como el método de comparación para la respuesta a una pregunta. Emparejar una pregunta y una resolución forma el paso de afirmación de sus pruebas.

Hubo un par de ejemplos de afirmaciones en nuestro Ejemplo completo, ambos realizados por Polly. Uno usó la Resolución Equals incorporada, mientras que el otro usó una Resolución IsPalpable personalizada. Examinemos esto último.

Todas las resoluciones heredan de la clase BaseResolution. Todo lo que se necesita es una línea y una función de “matcher”.

La línea aparece en el registro. Escriba la línea de tal manera que describa el valor esperado mientras completa la oración, “Esperando que sea…”. Puede usar {expectation} aquí para hacer referencia al valor esperado.

La función “matcher” puede provenir de PyHamcrest, o puede ser una funcionalidad personalizada escrita por usted.

De todos modos, IsPalpable usa el “matcher” personalizado HasSaturationGreaterThan. Así es como se pueden ensamblar:

# %load resolutions/matchers/has_saturation_greater_than.py
from typing import Any

from hamcrest.core.base_matcher import BaseMatcher
from hamcrest.core.description import Description


class HasSaturationGreaterThan(BaseMatcher):
    """Assert that a mood object has at least a specific saturation level."""

    def _matches(self, item: Any) -> bool:
        """Whether the assertion passes."""
        return self.saturation_level <= item.saturation

    def describe_to(self, description: Description) -> None:
        """Describe the passing case."""
        description.append_text(
            f"the mood has a saturation level of at least {self.saturation_level}"
        )

    def describe_mismatch(self, item: Any, mismatch_description: Description) -> None:
        """Description used when a match fails."""
        mismatch_description.append_text(
            f"the saturation level was less than {self.saturation_level}"
        )

    def describe_match(self, item: Any, match_description: Description) -> None:
        """Description used when a negated match fails."""
        match_description.append_text(
            f"the saturation level was at least {self.saturation_level}"
        )

    def __init__(self, saturation_level: int) -> None:
        self.saturation_level = saturation_level


def is_palpable() -> HasSaturationGreaterThan:
    return HasSaturationGreaterThan(85)

# %load resolutions/is_palpable.py
from screenpy.resolutions import BaseResolution

from .matchers.has_saturation_greater_than import is_palpable


class IsPalpable(BaseResolution):
    """Match a tension level that is very, very high!!!

    Examples::

        the_actor.should(See.the(AudienceTension(), IsPalpable()))
    """
    line = "a palpable tension!"
    matcher_function = is_palpable

¡Eso es realmente todo lo que hay que hacer para crear una Resolución!