Module presalytics
Presalytics Python Library
For more information, visit https://presalytics.io.
Overview
The Presalytics Python Library streamlines analytic operations across analysts, executives, consultants, and developers. These tools enable for a simplified workflow for analysts to rapidly generate client-ready presentation materials and web content that update in real-time and easily scale across your user-base.
Our objective when building this platform is make the analyst experience as simple as possible.
Set up time for new user should take less than half an hour, and all features are self-service and self-explanatory.
Of course, if you have any questions or need help to get going, you can contact us or quickly get the help you need on our slack channel (Join Here!).
To get going quickly, you can browse our Getting Started page and review some examples.
For more advanced users and developers, you can learn more about the API by reviewing the service structure to build a better understanding of the API and its security features.
Installation
The Presalytics Python Library is avialabe in python package index, and can be installed via pip:
pip install presalytics
Contributing
Presalytics.io is on Github. Bug reports and pull requests are strongly encouraged at the package repository. If you encounter any problems or have any suggestions for the API endpoints that this libary interacts with at https://api.presalytics.io, please open an issue in the API repository.
License
The Presalytics Python Library can be used in any of your applications and is covered by the MIT License. This library exchanges infromation with other web APIs that may be proprietary and carry their own licensing restriction. More more information on licensing, please contact inquires@presalytics.io.
Expand source code Browse git
"""
# Presalytics Python Library
For more information, visit https://presalytics.io.
# Overview
The Presalytics Python Library streamlines analytic operations across analysts, executives, consultants,
and developers. These tools enable for a simplified workflow for analysts to rapidly generate
client-ready presentation materials and web content that update in real-time and easily scale across your user-base.
Our objective when building this platform is make the analyst experience as simple as possible.
Set up time for new user should take less than half an hour, and all features are self-service and self-explanatory.
Of course, if you have any questions or need help to get going, you can [contact us](/contact-us) or
quickly get the help you need on our [slack channel](https://presalytics.slack.com)
([Join Here!](https://join.slack.com/t/presalytics/shared_invite/enQtODExMjc3MDE1Nzc5LWU0ZDlhZTgwZTM3MzQ4Yzc4Nzk4Zjc0NmQ3YjgzNTEwODdlYjM0ZjFkZWI4Y2ZhNzBmOTZhMzA2MzE3YjFiZTg)).
To get going quickly, you can browse our [Getting Started](https://presalytics.io/docs/getting-started/) page and review
some [examples](https://presalytics.io/docs/examples).
For more advanced users and developers, you can learn more about the API by reviewing
the [service structure](https://presalytics.io/docs/how-it-works) to build a better understanding
of the API and its [security](https://presalytics.io/docs/develpers/security) features.
# Installation
The Presalytics Python Library is avialabe in python package index, and can be installed via pip:
~~~~python
pip install presalytics
~~~~
# Contributing
Presalytics.io is on [Github](https://github.com/presalytics). Bug reports and pull requests are strongly encouraged at
the package [repository](https://github.com/presalytics/python-client). If you encounter any problems or have any suggestions for
the API endpoints that this libary interacts with at https://api.presalytics.io, please open an issue in the
[API repository](https://github.com/presalytics/Presalytics-API).
# License
The Presalytics Python Library can be used in any of your applications and is covered by the MIT License. This library
exchanges infromation with other web APIs that may be proprietary and carry their own licensing restriction. More more
information on licensing, please contact [inquires@presalytics.io](mailto:inquires@presalytics.io).
"""
import os
import environs
import logging
import pkg_resources
import presalytics.lib
import presalytics.lib.logger
import presalytics.lib.plugins
import presalytics.lib.plugins.base
import presalytics.lib.config_loader
import presalytics.story
import presalytics.story.components
env = environs.Env()
env.read_env()
# A comma-separated list of paths to search of config.py, plugin classes, and component classes
autodiscover_paths = env.list('AUTODISCOVER_PATHS', [])
CONFIG = presalytics.lib.config_loader.load_config(additional_paths=autodiscover_paths)
"""
Nested `dict` containing runtime configuration values for the Presalytics Python Library.
Typically, these are reuseable values stored in separate file that are loaded when the
module in imported via the `import presalytics` command. This top-level module then
runs the `presalytics.lib.config_loader.load_config` method into load values into the
the `CONFIG` global variable. Modules through the package use the `CONFIG` variable to
simplify their API calls store conststants for use throughout the package. See
`presalytics.lib.config_loader.load_config` for ways to programmatically load the
`CONFIG`.
Configuration Values
----------
USE_LOGGER : bool, optional
Toggles whether the presalytics verbose file logger should be used. Helpful for
tracing exceptions while writing code. Default is True.
LOG_LEVEL : str, optional
Defaults to `DEBUG`
USERNAME : str, optional
The user's Presalytics API email/username. This is the email address that the user uses when logging in at
https://login.presalytics.io. Will be passed to instances of the `presalytics.client.api.Client` object.
PASSWORD : str, optional
The user's Presalytics API username. Will be passed to instances of the
`presalytics.client.api.Client` object. If running in an insecure or
multiuser environment, leave this blank and let the `presalytics.client.api.Client`
object handle token acquisition via browser-based login.
DELEGATE_LOGIN: bool, optional
Defaults to False. Indicates whether the client would redirect to a browser to
acquire an API token. If `DELEGATE_LOGIN` is `True`, when the `presalytics.client.api.Client` does not have
access to a valid API token, the client will raise a `presalytics.lib.exceptions.InvalidTokenException`.
The default operation will automatically open a new browser tab to acquire a new token
via website client from the presalytics.io login page. Putting this setting to True is
useful for server-side development.
CACHE_TOKENS: bool, optional
Defaults to True. Indicates whether the `presalytics.client.api.Client` should store
tokens in the current working directory in a file call "token.json". This should be
set to False for mutli-user environments.
CLIENT_ID : str, optional
For developer use. Allows developers to implement a `client_credentials` OpenID
Connect login. Defaults to "python-client".
CLIENT_SECRET : str, optional
For developer use. Allows developers to implement a `client_credentials` OpenID
Connect login. Defaults to None.
VERIFY_HTTPS : bool, optional
For developer use. Allows for unencrypted connections. Defaults to True. No
reason to turn this to False unless you're in a complex development scenario
and you know what you're doing.
HOSTS : dict, optional
For developer use. Allows API class to target hosts other than api.presalytics.io
REDIRECT_URI : string, optional
For developer use. Useful if implementing authorization code flow for and OpenID Connect client.
Redirect URIs must be approved by Presalytics API devops for use in client applications.
RESERVED_NAMES: list of str, optional
A list of filenames for *.py files in the current workspace that should be ignored by the
registries.
IGNORE_PATHS: list of str, optional
A list of paths to not to include in registry autosdiscover
BROWSER_API_HOST: dict, optional
If present, the root url for browser-based api calls. Each service can have an independent browser host.
Service keys include `OOXML_AUTOMATION`, `STORY`, `DOC_CONVERTER`, and `SITE`. May be required when services are running on a cluster that
deletegates certificate authentication to an external service or in debug/test environments.
See for more info: https://developer.mozilla.org/en-US/docs/Web/Security/Mixed_content
The object can also take on values for user-defined extensions, and please consult
the documentation for those package for those vairables definition.
"""
file_logger = CONFIG.get("USE_LOGGER", True)
log_level = CONFIG.get("LOG_LEVEL", logging.DEBUG)
presalytics.lib.logger.configure_logger(log_level=log_level, file_logger=file_logger)
registry_kwargs = {
'show_errors': False,
'autodiscover_paths': autodiscover_paths,
'reserved_names': CONFIG.get("RESERVED_NAMES", []),
'ignore_paths': CONFIG.get("IGNORE_PATHS", [])
}
PLUGINS = presalytics.lib.plugins.base.PluginRegistry(**registry_kwargs)
"""
Instance of `presalytics.lib.plugins.base.PluginRegistry`. A container listing the
Presalytics Library Plugins available and loaded in this environment. This instance is used
by `presalytics.story.components.Renderer` subclasses (e.g., `presalytics.story.revealer.Revealer`)
to write scripts and links into stories.
"""
COMPONENTS = presalytics.story.components.ComponentRegistry(**registry_kwargs)
"""
Instance of `presalytics.story.components.ComponentRegistry`. Registry for Library components and
component instances. A container listing the Presalytics Library components and instances available
and loaded in this environment. This instance is used by `presalytics.story.components.Renderer` subclasses
(e.g., `presalytics.story.revealer.Revealer`) to convert widgets, pages, and themes into stories.
"""
try:
__version__ = pkg_resources.require(presalytics.__name__)[0].version
except Exception:
__version__ = "build"
from presalytics.client.api import Client
from presalytics.lib.plugins.base import PluginBase
from presalytics.lib.plugins.external import ApprovedExternalLinks, ApprovedExternalScripts
from presalytics.lib.plugins.jinja import JinjaPluginMakerMixin
from presalytics.lib.plugins.local import LocalStylesPlugin
from presalytics.lib.plugins.matplotlib import Mpld3Plugin
from presalytics.lib.plugins.ooxml import OoxmlTheme
from presalytics.lib.plugins.reveal import RevealConfigPlugin
from presalytics.lib.plugins.reveal_theme import RevealCustomTheme
from presalytics.lib.plugins.scss import ScssPlugin
from presalytics.lib.templates.base import (
JinjaTemplateBuilder,
BootstrapCustomTemplate
)
from presalytics.lib.widgets.matplotlib import MatplotlibFigure, MatplotlibResponsiveFigure
from presalytics.lib.widgets.d3 import (
D3Widget
)
from presalytics.lib.widgets.ooxml import (
OoxmlWidgetBase,
OoxmlFileWidget,
OoxmlEndpointMap,
ChartUpdaterWidget,
TableUpdaterWidget
)
from presalytics.story.outline import StoryOutline
from presalytics.story.revealer import Revealer
from presalytics.story.components import WidgetBase, PageTemplateBase, Renderer, ThemeBase
from presalytics.lib.tools.ooxml_tools import (
create_story_from_ooxml_file
)
from presalytics.lib.tools.story_tools import story_post_file_bytes
from presalytics.lib.tools.component_tools import (
create_outline_from_page,
create_outline_from_widget
)
from presalytics.lib.widgets.ooxml_editors import (
OoxmlEditorWidget,
XmlTransformBase,
ChangeShapeColor,
TextReplace,
MultiXmlTransform
)
__all__ = [
'CONFIG',
'COMPONENTS',
'PLUGINS',
'Client',
'StoryOutline',
'Renderer',
'Revealer',
'MatplotlibFigure',
'MatplotlibResponsiveFigure',
'OoxmlFileWidget',
'OoxmlEndpointMap',
'OoxmlWidgetBase',
'OoxmlEditorWidget',
'D3Widget',
'ChartUpdaterWidget',
'TableUpdaterWidget',
'XmlTransformBase',
'ChangeShapeColor',
'TextReplace',
'MultiXmlTransform',
'ApprovedExternalLinks',
'ApprovedExternalScripts',
'LocalStylesPlugin',
'OoxmlTheme',
'RevealConfigPlugin',
'RevealCustomTheme',
'JinjaTemplateBuilder',
'WidgetBase',
'PageTemplateBase',
'ScssPlugin',
'create_story_from_ooxml_file',
'story_post_file_bytes',
'create_outline_from_page',
'create_outline_from_widget'
]
Sub-modules
presalytics.cli
-
Command Line Interface for Presalytics Python Library …
presalytics.client
-
This module conatains objects for interacting with the Presalytics API. It has three submodules:
presalytics.client.presalytics_doc_converter
, … presalytics.lib
-
Contains configuration and library objects for users to build upon when create story objects
presalytics.story
-
Conains base objects for rendering, building, serializing, and view Story objects
Global variables
var CONFIG
-
Nested
dict
containing runtime configuration values for the Presalytics Python Library. Typically, these are reuseable values stored in separate file that are loaded when the module in imported via theimport presalytics
command. This top-level module then runs theload_config()
method into load values into the theCONFIG
global variable. Modules through the package use theCONFIG
variable to simplify their API calls store conststants for use throughout the package. Seeload_config()
for ways to programmatically load theCONFIG
.Configuration Values
USE_LOGGER
:bool
, optional- Toggles whether the presalytics verbose file logger should be used. Helpful for tracing exceptions while writing code. Default is True.
LOG_LEVEL
:str
, optional- Defaults to
DEBUG
USERNAME
:str
, optional- The user's Presalytics API email/username.
This is the email address that the user uses when logging in at
https://login.presalytics.io.
Will be passed to instances of the
Client
object. PASSWORD
:str
, optional- The user's Presalytics API username.
Will be passed to instances of the
Client
object. If running in an insecure or multiuser environment, leave this blank and let theClient
object handle token acquisition via browser-based login. DELEGATE_LOGIN
:bool
, optional- Defaults to False.
Indicates whether the client would redirect to a browser to
acquire an API token. If
DELEGATE_LOGIN
isTrue
, when theClient
does not have access to a valid API token, the client will raise aInvalidTokenException
. The default operation will automatically open a new browser tab to acquire a new token via website client from the presalytics.io login page. Putting this setting to True is useful for server-side development. CACHE_TOKENS
:bool
, optional- Defaults to True.
Indicates whether the
Client
should store tokens in the current working directory in a file call "token.json". This should be set to False for mutli-user environments. CLIENT_ID
:str
, optional- For developer use. Allows developers to implement a
client_credentials
OpenID Connect login. Defaults to "python-client". CLIENT_SECRET
:str
, optional- For developer use. Allows developers to implement a
client_credentials
OpenID Connect login. Defaults to None. VERIFY_HTTPS
:bool
, optional- For developer use. Allows for unencrypted connections. Defaults to True. No reason to turn this to False unless you're in a complex development scenario and you know what you're doing.
HOSTS
:dict
, optional- For developer use. Allows API class to target hosts other than api.presalytics.io
REDIRECT_URI
:string
, optional- For developer use. Useful if implementing authorization code flow for and OpenID Connect client. Redirect URIs must be approved by Presalytics API devops for use in client applications.
RESERVED_NAMES
:list
ofstr
, optional- A list of filenames for *.py files in the current workspace that should be ignored by the registries.
IGNORE_PATHS
:list
ofstr
, optional- A list of paths to not to include in registry autosdiscover
BROWSER_API_HOST
:dict
, optional- If present, the root url for browser-based api calls.
Each service can have an independent browser host.
Service keys include
OOXML_AUTOMATION
,STORY
,DOC_CONVERTER
, andSITE
. May be required when services are running on a cluster that deletegates certificate authentication to an external service or in debug/test environments.
See for more info: https://developer.mozilla.org/en-US/docs/Web/Security/Mixed_content
The object can also take on values for user-defined extensions, and please consult the documentation for those package for those vairables definition.
var COMPONENTS
-
Instance of
ComponentRegistry
. Registry for Library components and component instances. A container listing the Presalytics Library components and instances available and loaded in this environment. This instance is used byRenderer
subclasses (e.g.,Revealer
) to convert widgets, pages, and themes into stories. var PLUGINS
-
Instance of
PluginRegistry
. A container listing the Presalytics Library Plugins available and loaded in this environment. This instance is used byRenderer
subclasses (e.g.,Revealer
) to write scripts and links into stories.
Functions
def create_story_from_ooxml_file(filename, client_info={})
-
Utility Method for building stories into the Presalytics API directly from a Presentation or Spreadsheet file.
Parameters
filename
:str
- A string contain the local path to a presenation or spreadsheet object.
client_info
:dict
, optional- A dictionary containing arguments that will be unpacked and passed to a
Client
object on intialization. This dictionary can include thetoken
,cache_tokens
,delegate_login
values. SeeClient
for more information.
Returns:
A
Story
containing information about the Story object in the Presalytics APIExpand source code Browse git
def create_story_from_ooxml_file(filename: str, client_info={}) -> 'Story': """ Utility Method for building stories into the Presalytics API directly from a Presentation or Spreadsheet file. Parameters ---------- filename : str A string contain the local path to a presenation or spreadsheet object. client_info : dict, optional A dictionary containing arguments that will be unpacked and passed to a `presalytics.client.api.Client` object on intialization. This dictionary can include the `token`, `cache_tokens`, `delegate_login` values. See `presalytics.client.api.Client` for more information. Returns: ---------- A `presalytics.client.presalytics_story.models.story.Story` containing information about the Story object in the Presalytics API """ story: 'Story' logger.info("Starting presalytics tool: create_story_from_ooxml_file") logger.info("Intializing presalytics client.") client = presalytics.Client(**client_info) logger.info("Sending file to presalytics server for document processing and base story creation") story = client.story.story_post_file(file=filename) logger.info("Creating local instances of file widgets") outline = presalytics.StoryOutline.load(story.outline) for i in range(0, len(outline.pages)): page = outline.pages[i] for j in range(0, len(page.widgets)): widget = page.widgets[j] logger.info('Creating OoxmlFileWidget with name "{}"'.format(widget.name)) inst = presalytics.OoxmlFileWidget.deserialize(widget, **client_info) presalytics.COMPONENTS.register(inst) logger.info('Rewriting outline with widget: "{}"'.format(widget.name)) outline.pages[i].widgets[j] = inst.serialize() story.outline = outline.dump() return story
def story_post_file_bytes(client, binary_obj, filename, mime_type=None)
-
Create a Presalytics API Story object from a file-like
io.BytesIO
object. Helpful for server-side interaction with the Presalytics Story APIParameters
presalytics.client
:Client
- A client object for making api calls
binary_obj
:io.BytesIO
- A file-like object for storing file-data in memory. Often found in multipart messages uploaded from browsers.
filename
:str
- The filename of the object to be uploaded
mimetype
:str
, optional- If known, please add the mimetype of the file. Otherwise, this method will execute an additional API call ascertain the file's mimetype
Returns
A
Story
containing information about the Story object in the Presalytics APIExpand source code Browse git
def story_post_file_bytes(client: 'Client', binary_obj: 'BytesIO', filename: str, mime_type: str = None): """ Create a Presalytics API Story object from a file-like `io.BytesIO` object. Helpful for server-side interaction with the Presalytics Story API Parameters ---------- client : presalytics.client.api.Client A client object for making api calls binary_obj : io.BytesIO A file-like object for storing file-data in memory. Often found in multipart messages uploaded from browsers. filename : str The filename of the object to be uploaded mimetype : str, optional If known, please add the mimetype of the file. Otherwise, this method will execute an additional API call ascertain the file's mimetype Returns ---------- A `presalytics.client.presalytics_story.models.story.Story` containing information about the Story object in the Presalytics API """ if not mime_type: mime_type = presalytics.lib.tools.ooxml_tools.get_mime_type_from_filename(client, filename) _file = {'file': (filename, binary_obj, mime_type,)} headers = client.get_auth_header() headers.update(client.get_request_id_header()) headers.update({ 'User-Agent': client.story.api_client.user_agent, 'Accept': 'application/json' }) endpoint = urllib.parse.urljoin(client.story.api_client.configuration.host, 'story/file') try: resp = requests.post(endpoint, headers=headers, files=_file) except Exception as ex: message = "An error occured in the presalytics API client" if locals().get("resp", None): code = resp.status_code else: code = 500 raise presalytics.lib.exceptions.ApiError(message=message, status_code=code) data = resp.json() if resp.status_code > 299: logger.error(data['detail']) raise presalytics.lib.exceptions.ApiError(message=data["detail"], status_code=resp.status_code) else: try: story = client.story.api_client._ApiClient__deserialize(data, 'Story') return story except Exception as ex: logger.error("Story object could not be deserialized.") logger.exception(ex) return data
def create_outline_from_page(page, title=None, description=None)
-
Creates a
StoryOutline
from a subclass ofPageTemplateBase
. Useful for quick starts, demos, and rapidly sharing content without having to manaully build aStoryOutline
.Parameters
page
:subclass
ofPageTemplateBase
- The Page component that you want to build the story from
title
:str
, optional- The title of the
StoryOutline
. Defaults to the name of the page. description
:str
, optional- The description of the story. Autopopulated if unassigned.
Expand source code Browse git
def create_outline_from_page(page: 'PageTemplateBase', title: str = None, description: str = None) -> 'StoryOutline': """ Creates a `presalytics.story.outline.StoryOutline` from a subclass of `presalytics.story.components.PageTemplateBase`. Useful for quick starts, demos, and rapidly sharing content without having to manaully build a `StoryOutline`. Parameters ---------- page : subclass of presalytics.story.components.PageTemplateBase The Page component that you want to build the story from title : str, optional The title of the `presalytics.story.outline.StoryOutline`. Defaults to the name of the page. description : str, optional The description of the story. Autopopulated if unassigned. """ info = presalytics.story.outline.Info( revision="0", date_created=datetime.datetime.now().astimezone(datetime.timezone.utc).isoformat(), date_modified=datetime.datetime.now().astimezone(datetime.timezone.utc).isoformat(), created_by=presalytics.CONFIG["USERNAME"], modified_by=presalytics.CONFIG["USERNAME"], revision_notes="Created by 'create_outline_from_page' method" ) page_outline = page.serialize() if not description: description = "{0} created by 'create_outline_from_page' method".format(page_outline.name) if not title: title = page_outline.name outline = presalytics.story.outline.StoryOutline( info=info, pages=[page], description=description, title=title, themes=[] ) return outline
def create_outline_from_widget(widget, page_name=None, title=None, description=None)
-
Creates a
StoryOutline
from a subclass ofWidgetBase
. Useful for quickstarts, demos, and rapidly sharing content without having to manaully build aStoryOutline
.Parameters
widget
:subclass
ofWidgetBase
- The widget component that you want to build the story from
page_name
:str
, optional- The name you want to give the
Page
in the outline. Defaults to the name of the suppliedwidget
title
:str
, optional- The title of the
StoryOutline
. Defaults topage_name
. description
:str
, optional- The description of the story. Autopopulated if unassigned.
Expand source code Browse git
def create_outline_from_widget(widget: 'WidgetBase', page_name: str = None, title: str = None, description: str = None) -> 'StoryOutline': """ Creates a `presalytics.story.outline.StoryOutline` from a subclass of `presalytics.story.components.WidgetBase`. Useful for quickstarts, demos, and rapidly sharing content without having to manaully build a `StoryOutline`. Parameters ---------- widget : subclass of presalytics.story.components.WidgetBase The widget component that you want to build the story from page_name : str, optional The name you want to give the `presalytics.story.outline.Page` in the outline. Defaults to the name of the supplied `widget` title : str, optional The title of the `presalytics.story.outline.StoryOutline`. Defaults to `page_name`. description : str, optional The description of the story. Autopopulated if unassigned. """ info = presalytics.story.outline.Info( revision="0", date_created=datetime.datetime.now().astimezone(datetime.timezone.utc).isoformat(), date_modified=datetime.datetime.now().astimezone(datetime.timezone.utc).isoformat(), created_by=presalytics.CONFIG["USERNAME"], modified_by=presalytics.CONFIG["USERNAME"], revision_notes="Created by 'create_outline_from_widget' method" ) outline_widget = widget.serialize() if not page_name: page_name = outline_widget.name page = presalytics.story.outline.Page( name=page_name, kind="widget-page", widgets=[outline_widget] ) if not description: description = "{0} with {1} created by 'create_outline_from_widget' method".format(page_name, outline_widget.name) if not title: title = page_name outline = presalytics.story.outline.StoryOutline( info=info, pages=[page], description=description, title=title, themes=[] ) return outline
Classes
class Client (username=None, password=None, delegate_login=False, token=None, cache_tokens=False, client_id=None, client_secret=None, **kwargs)
-
Class for interacting with Presalytics API endpoints
The Client class creates a simple interface for user to interactive with the Presalytics API and is the primary building block for user-built automation of stories, dashboards, and interactive presentations.
A client instance wraps python functions around Presalytics API endpoints and manages user authentication. On initialization, he client checks the status of a user authentication the expiry of their refresh an access tokens. When needed, the client will open a browser to prompt the user to login at the presalytics.io login page (or raise an
InvalidTokenException
whendelegate_login
isTrue
).After authenication, users can call the methods bound to the story, ooxml_automation, and doc_converter attributes to make calls in into the Presalytics API.
A note for server-side development:
The client class can automatically cache tokens in a file called "token.json", located in the python's current working directory. This is done so users running scripts accross multiple client instances do not have to acquire a new token every time an API call is made. If building a client to operate in a multi-user environment, this behavior should be turned off so that one user cannot not pull one another's tokens. To do this, ensure the following parameters are pass to the configuration either via initialization or in a
CONFIG
file:cache_tokens = False, delegate_login = True
When delegate login is True, the client assumes that the application creating instances of the client object will handle user authentication. The simplest way to do this is to pass a token to the client via the "token" keyword argument.
Parameters
username
:str
, optional- Defaults to None.
The user's Presalytics API username.
This keyword will take precedence over a passed to the client
via
CONFIG
. The username must either be present inCONFIG
or be passed in via keyword, otherwise the client will raise aMissingConfigException
. password
:str
, optional- Defaults to None.
The user's Presalytics API password.
This useful for quickly testing scripts, but in most
scenario users should not be passing plaintext into the client via this keyword.
In a secure, single-user
environment, passwords are better placed in the
CONFIG
object for reuseability. A more secure is to leave passwords out of the configuration, keepdelegate_login
=False
, and acquire tokens via the browser. delegate_login
:bool
, optional- Defaults to False.
Indicates whether the client would redirect to a browser to
acquire an API token. If
DELEGATE_LOGIN
isTrue
, when theClient
does not have access to a valid API token, the client will raise aInvalidTokenException
. The default operation will automatically open a new browser tab to acquire a new token via website client from the presalytics.io login page. Putting this setting to True is useful for server-side development. token
:dict
, optional-
Defaults to None. A dictionary contain information about tokens acquire from auth.presalytics.io. The dictionary must contain an
access_token
, arefresh_token
, and entries contiaing information about token expiry.Token expiry information can either passed in ISO 8601 formatted string with a UTC offset as dictionary keys
access_token_expire_time
andrefresh_token_expire_time
or an integer in seconds with the corresponding dictionary keysexpires_in
andrefresh_expires_in
.if the
dict
passed in via this keywork does is not have the correct entries, the client will raise anInvalidTokenException
. cache_tokens
:bool
, optional- Defaults to False. Toggles whether or the client should cache its acquired tokens in file called "token.json" in the current working directory. Minimizes the number of times a user is required to login. Set to False in multi-user environments.
Attributes
direct_grant
:bool
- Indicates whether an token will be acquire via the "direct_grant" OpenID Connect flow.
Usually indicates
whether the user has supplied a passwork to the client either through
CONFIG
ro during object initialization. doc_converter
:DefaultApi
-
Interface to the Presalytics API Doc Converter service. The object contains methods that enable the client to make api calls that return deserialized objects from the Presalytics API, simplying user and developer interaction with the Presaltytics API. API calls can be generated as follows:
client = presalytics.Client() api_obj = client.doc_converter.{operation_id}(*args)
where
{operation_id}
is theoperationId
assocated with the endpoint specified the Doc Converter Service OpenAPI Contract , and *args are the corresponding arguments that are passed to the method. A complete list of the avialable methods is shown on theDefaultApi
object.Note: This attribute contains automatically generated methods via the OpenAPI generator. The
DefaultApi
has been passed an anapi_client
keyword argument with an instance ofDocConverterApiClientWithAuth
, which adds an authentication and request processing middleware layer to the default sub package built via code generatation. ooxml_automation
:DefaultApi
-
Interface to the Presalytics API Ooxml Automation service. The object contains methods that enable the client to make api calls that return deserialized objects from the Presalytics API, simplying user and developer interaction with the Presaltytics API. API calls can be generated as follows:
client = presalytics.Client() api_obj = client.ooxml_automation.{operation_id}(*args)
where
{operation_id}
is theoperationId
assocated with the endpoint specified the Ooxml Automation Service OpenAPI Contract , and *args are the corresponding arguments that are passed to the method. A complete list of the avialable methods is shown on theDefaultApi
object.Note: This attribute contains automatically generated methods via the OpenAPI generator. The
DefaultApi
has been passed an anapi_client
keyword argument with an instance ofOoxmlAutomationApiClientWithAuth
, which adds an authentication and request processing middleware layer to the default sub package built via code generatation. presalytics.story
:DefaultApi
-
Interface to the Presalytics API Ooxml Automation service. The object contains methods that enable the client to make api calls that return deserialized objects from the Presalytics API, simplying user and developer interaction with the Presaltytics API. API calls can be generated as follows:
client = presalytics.Client() api_obj = client.story.{operation_id}(*args)
where
{operation_id}
is theoperationId
assocated with the endpoint specified the Ooxml Automation Service OpenAPI Contract , and *args are the corresponding arguments that are passed to the method. A complete list of the avialable methods is shown on theDefaultApi
object.Note: This attribute contains automatically generated methods via the OpenAPI generator. The
DefaultApi
has been passed an anapi_client
keyword argument with an instance ofStoryApiClientWithAuth
, which adds an authentication and request processing middleware layer to the default sub package built via code generatation. client_id
:str
- The client_id that is used OpenID Connect login. Defaults to "python-client".
client_secret
:str
, optional- The client_secret used during OpenID Connect login.
Useful
confidential_client
is True. confidential_client
:bool
- Indicates whether a this client can obtain tokens from auth.presalytics.io without a user under
OpenID Connect grant type "confidential_client".
Requires a
client_secret
. Default is False. oidc
:presalytics.client.oidc.OidcClient
- A middleware class to help acquire and validate tokens from login.presalytics.io.
token_util
:presalytics.client.auth.TokenUtil
- A handler for managing an caching tokens acquired from auth.presalytics.io.
site_host
:str
- The login site host for acquiring tokens.
Set from
CONFIG
with keyword["SITE"]["HOST"]
. Defaults to https://presalytics.io. redirect_uri
:str
- Useful if implementing authorization code flow for and OpenID Connect client.
Redirect URIs must
be approved by Presalytics API devops for use in client applications. Set from Set from
CONFIG
with keyword["REDIRECT_URI"]
. Defaults to https://presalytics.io/user/login-success. login_sleep_interval
:int
- The duration (in seconds) between attempts to acquire a token after browser-based authentication. Defaults to 5 seconds.
login_timeout
:int
- Defaults to 60 seconds.
The amount of time the client will attempt to acquire a token after the
https://presalytics.io authenicates a user. Raises a
LoginTimeout
if the user has not authenticated by the time the interval has expired.
Expand source code Browse git
class Client(object): """ Class for interacting with Presalytics API endpoints The Client class creates a simple interface for user to interactive with the Presalytics API and is the primary building block for user-built automation of stories, dashboards, and interactive presentations. A client instance wraps python functions around Presalytics API endpoints and manages user authentication. On initialization, he client checks the status of a user authentication the expiry of their refresh an access tokens. When needed, the client will open a browser to prompt the user to login at the presalytics.io login page (or raise an `presalytics.lib.exceptions.InvalidTokenException` when `delegate_login` is `True`). After authenication, users can call the methods bound to the story, ooxml_automation, and doc_converter attributes to make calls in into the Presalytics API. *A note for server-side development*: The client class can automatically cache tokens in a file called "token.json", located in the python's current working directory. This is done so users running scripts accross multiple client instances do not have to acquire a new token every time an API call is made. If building a client to operate in a multi-user environment, this behavior should be turned off so that one user cannot not pull one another's tokens. To do this, ensure the following parameters are pass to the configuration either via initialization or in a `presalytics.CONFIG` file: cache_tokens = False, delegate_login = True When delegate login is True, the client assumes that the application creating instances of the client object will handle user authentication. The simplest way to do this is to pass a token to the client via the "token" keyword argument. Parameters ---------- username : str, optional Defaults to None. The user's Presalytics API username. This keyword will take precedence over a passed to the client via `presalytics.CONFIG`. The username must either be present in `presalytics.CONFIG` or be passed in via keyword, otherwise the client will raise a `presalytics.lib.exceptions.MissingConfigException`. password : str, optional Defaults to None. The user's Presalytics API password. This useful for quickly testing scripts, but in most scenario users should not be passing plaintext into the client via this keyword. In a secure, single-user environment, passwords are better placed in the `presalytics.CONFIG` object for reuseability. A more secure is to leave passwords out of the configuration, keep `delegate_login` = `False`, and acquire tokens via the browser. delegate_login : bool, optional Defaults to False. Indicates whether the client would redirect to a browser to acquire an API token. If `DELEGATE_LOGIN` is `True`, when the `presalytics.client.api.Client` does not have access to a valid API token, the client will raise a `presalytics.lib.exceptions.InvalidTokenException`. The default operation will automatically open a new browser tab to acquire a new token via website client from the presalytics.io login page. Putting this setting to True is useful for server-side development. token : dict, optional Defaults to None. A dictionary contain information about tokens acquire from auth.presalytics.io. The dictionary must contain an `access_token`, a `refresh_token`, and entries contiaing information about token expiry. Token expiry information can either passed in ISO 8601 formatted string with a UTC offset as dictionary keys `access_token_expire_time` and `refresh_token_expire_time` or an integer in seconds with the corresponding dictionary keys`expires_in` and `refresh_expires_in`. if the `dict` passed in via this keywork does is not have the correct entries, the client will raise an `presalytics.lib.exceptions.InvalidTokenException`. cache_tokens : bool, optional Defaults to False. Toggles whether or the client should cache its acquired tokens in file called "token.json" in the current working directory. Minimizes the number of times a user is required to login. Set to False in multi-user environments. Attributes ---------- direct_grant : bool Indicates whether an token will be acquire via the "direct_grant" OpenID Connect flow. Usually indicates whether the user has supplied a passwork to the client either through `presalytics.CONFIG` ro during object initialization. doc_converter : presalytics.client.presalytics_doc_converter.api.default_api.DefaultApi Interface to the Presalytics API Doc Converter service. The object contains methods that enable the client to make api calls that return deserialized objects from the Presalytics API, simplying user and developer interaction with the Presaltytics API. API calls can be generated as follows: client = presalytics.Client() api_obj = client.doc_converter.{operation_id}(*args) where `{operation_id}` is the `operationId` assocated with the endpoint specified the [Doc Converter Service OpenAPI Contract](https://presalytics.io/docs/api-specifications/doc-converter/) , and *args are the corresponding arguments that are passed to the method. A complete list of the avialable methods is shown on the `presalytics.client.presalytics_doc_converter.api.default_api.DefaultApi` object. *Note*: This attribute contains automatically generated methods via the [OpenAPI generator](https://github.com/OpenAPITools/openapi-generator). The `presalytics.client.presalytics_doc_converter.api.default_api.DefaultApi` has been passed an an `api_client` keyword argument with an instance of `presalytics.client.api.DocConverterApiClientWithAuth`, which adds an authentication and request processing middleware layer to the default sub package built via code generatation. ooxml_automation : presalytics.client.presalytics_ooxml_automation.api.default_api.DefaultApi Interface to the Presalytics API Ooxml Automation service. The object contains methods that enable the client to make api calls that return deserialized objects from the Presalytics API, simplying user and developer interaction with the Presaltytics API. API calls can be generated as follows: client = presalytics.Client() api_obj = client.ooxml_automation.{operation_id}(*args) where `{operation_id}` is the `operationId` assocated with the endpoint specified the [Ooxml Automation Service OpenAPI Contract](https://presalytics.io/docs/api-specifications/ooxml-automation/) , and *args are the corresponding arguments that are passed to the method. A complete list of the avialable methods is shown on the `presalytics.client.presalytics_ooxml_automation.api.default_api.DefaultApi` object. *Note*: This attribute contains automatically generated methods via the [OpenAPI generator](https://github.com/OpenAPITools/openapi-generator). The `presalytics.client.presalytics_ooxml_automation.api.default_api.DefaultApi` has been passed an an `api_client` keyword argument with an instance of `presalytics.client.api.OoxmlAutomationApiClientWithAuth`, which adds an authentication and request processing middleware layer to the default sub package built via code generatation. story : presalytics.client.presalytics_story.api.default_api.DefaultApi Interface to the Presalytics API Ooxml Automation service. The object contains methods that enable the client to make api calls that return deserialized objects from the Presalytics API, simplying user and developer interaction with the Presaltytics API. API calls can be generated as follows: client = presalytics.Client() api_obj = client.story.{operation_id}(*args) where `{operation_id}` is the `operationId` assocated with the endpoint specified the [Ooxml Automation Service OpenAPI Contract](https://presalytics.io/docs/api-specifications/story/) , and *args are the corresponding arguments that are passed to the method. A complete list of the avialable methods is shown on the `presalytics.client.presalytics_story.api.default_api.DefaultApi` object. *Note*: This attribute contains automatically generated methods via the [OpenAPI generator](https://github.com/OpenAPITools/openapi-generator). The `presalytics.client.presalytics_story.api.default_api.DefaultApi` has been passed an an `api_client` keyword argument with an instance of `presalytics.client.api.StoryApiClientWithAuth`, which adds an authentication and request processing middleware layer to the default sub package built via code generatation. client_id : str The client_id that is used OpenID Connect login. Defaults to "python-client". client_secret : str, optional The client_secret used during OpenID Connect login. Useful `confidential_client` is True. confidential_client : bool Indicates whether a this client can obtain tokens from auth.presalytics.io without a user under OpenID Connect grant type "confidential_client". Requires a `client_secret`. Default is False. oidc : `presalytics.client.oidc.OidcClient` A middleware class to help acquire and validate tokens from login.presalytics.io. token_util : `presalytics.client.auth.TokenUtil` A handler for managing an caching tokens acquired from auth.presalytics.io. site_host : str The login site host for acquiring tokens. Set from `presalytics.CONFIG` with keyword `["SITE"]["HOST"]`. Defaults to https://presalytics.io. redirect_uri : str Useful if implementing authorization code flow for and OpenID Connect client. Redirect URIs must be approved by Presalytics API devops for use in client applications. Set from Set from `presalytics.CONFIG` with keyword `["REDIRECT_URI"]`. Defaults to https://presalytics.io/user/login-success. login_sleep_interval : int The duration (in seconds) between attempts to acquire a token after browser-based authentication. Defaults to 5 seconds. login_timeout : int Defaults to 60 seconds. The amount of time the client will attempt to acquire a token after the https://presalytics.io authenicates a user. Raises a `presalytics.lib.exceptions.LoginTimeout` if the user has not authenticated by the time the interval has expired. """ def __init__( self, username=None, password=None, delegate_login=False, token=None, cache_tokens=False, client_id=None, client_secret=None, **kwargs): if username: self.username = username else: try: self.username = presalytics.CONFIG['USERNAME'] except KeyError: if token: self.username = None else: if not client_secret: raise presalytics.lib.exceptions.MissingConfigException("when not passing tokens directly, a clien must have either a client_secrect or a username") try: if password: self.password = password else: self.password = presalytics.CONFIG['PASSWORD'] self.direct_grant = True except KeyError: self.password = None self.direct_grant = False try: if client_id: self.client_id = client_id else: self.client_id = presalytics.CONFIG['CLIENT_ID'] except KeyError: self.client_id = cnst.DEFAULT_CLIENT_ID try: if client_secret: self.client_secret = client_secret else: self.client_secret = presalytics.CONFIG['CLIENT_SECRET'] self.confidential_client = True except KeyError: self.client_secret = None self.confidential_client = False try: self.site_host = presalytics.CONFIG["HOSTS"]["SITE"] except KeyError: self.site_host = cnst.SITE_HOST try: self.redirect_uri = presalytics.CONFIG["REDIRECT_URI"] except KeyError: self.redirect_uri = cnst.REDIRECT_URI if delegate_login or presalytics.CONFIG.get("DELEGATE_LOGIN", False): self._delegate_login = True else: self._delegate_login = False self.oidc = presalytics.client.oidc.OidcClient( client_id=self.client_id, client_secret=self.client_secret ) if presalytics.CONFIG.get("CACHE_TOKENS", None): cache_tokens = presalytics.CONFIG.get("CACHE_TOKENS") self.token_util = presalytics.client.auth.TokenUtil(token_cache=cache_tokens) if token: # Assume if token is passed as string, then it's an access token if isinstance(token, str): self.token_util.token = {"access_token": token} # if token is a dictionary with an 'access_token_expire_time' key, it's previous been processed / deserialized elif token.get('access_token_expire_time', None): self.token_util.token = token # if token has an 'expires_in' key, if has not been deserialized elif token.get('expires_in', None): self.token_util.process_token(token) else: raise presalytics.lib.exceptions.InvalidTokenException(message="Unknown token format.") if self.token_util.token_cache: self.token_util._put_token_file() if not self._delegate_login: self.token_util.token = self.refresh_token() doc_converter_api_client = DocConverterApiClientWithAuth(self, **kwargs) self.doc_converter = presalytics.client.presalytics_doc_converter.DefaultApi(api_client=doc_converter_api_client) ooxml_automation_api_client = OoxmlAutomationApiClientWithAuth(self, **kwargs) self.ooxml_automation = presalytics.client.presalytics_ooxml_automation.DefaultApi(api_client=ooxml_automation_api_client) story_api_client = StoryApiClientWithAuth(self, **kwargs) self.story = presalytics.client.presalytics_story.DefaultApi(api_client=story_api_client) def login(self): """ Triggers a an attempt to acquire an API token based on the the client configuration """ if self.direct_grant: token = self.oidc.token(username=self.username, password=self.password) else: token = self.oidc.token(username=self.username) self.token_util.process_token(token) return self.token_util.token def refresh_token(self): """ Obtains a new access token if the access token is expired. if refresh token is expired, this method prompt user to re-authenticate when `delegate_login` is `False` or raise an `presalytics.lib.exceptions.InvalidTokenException` when `deletegate_login` is True. """ if self.token_util.is_api_access_token_expired(): if self.token_util.token.get('refresh_token', None) and self.client_secret: refresh_token = self.token_util.token["refresh_token"] token = self.oidc.refresh_token(refresh_token) self.token_util.process_token(token) logger.debug("Refresh token granted successfully.") else: if self.direct_grant: token = self.oidc.token(username=self.username, password=self.password) elif self.confidential_client: token = self.oidc.client_credentials_token() elif self._delegate_login: raise presalytics.lib.exceptions.ApiError("Unauthorized. Token has expired", status_code=401) else: token = self.oidc.token(username=self.username) self.token_util.process_token(token) if self.token_util.token_cache: self.token_util._put_token_file() return self.token_util.token def get_auth_header(self): """ Creates a JWT Bearer Authorization token header Returns ---------- A `dict` authorization crediential to be attached to an API request """ self.refresh_token() auth_header = { "Authorization": "Bearer " + self.token_util.token["access_token"] } return auth_header def get_request_id_header(self): """ Creates an 'X-Request-Id' token header for tracing requests through Presalytics API services. If deployed alongside the [WSGI Microservice Middleware](https://github.com/presalytics/WSGI-Microservice-Middleware) package, this method will pull the request id from the call stack. Returns ---------- A `dict` header representation with an 'X-Request-Id' key to be attached to an API request """ current_request_id = wsgi_microservice_middleware.current_request_id() if not current_request_id: current_request_id = str(uuid4()) header = { "X-Request-Id": current_request_id } return header def download_file(self, story_id, ooxml_automation_id, download_folder=None, filename=None, **kwargs): """ Downloads an updated Ooxml Automation file and places the file in a designated folder Parameters --------- story : str The id of the Presalytics Story API object that manages access to document ooxml_automation_id : str The id of the Presalytics API Ooxml Automation service object that you want to download download_folder : str, optional The filepath to the local directory that you want to download the file to. Defaults to the current working directory. filename: str, optional The name of the downloaded file. Defaults to the original filename the the object was created. """ response, status, headers = self.story.story_id_file_ooxmlautomationid_get_with_http_info(story_id, ooxml_automation_id, _preload_content=False) if download_folder is None: download_folder = os.getcwd() if filename is None: cd_header = headers.get('Content-Disposition') _, params = cgi.parse_header(cd_header) filename = params["filename"] filepath = os.path.join(download_folder, filename) with open(filepath, 'wb') as f: f.write(response.data) def get_client_info(self): """ Convenience method returning information about this client to pass to downstream objects, e.g., components and new client instances Returns ---------- A dictionary containing instances values: - token: self.token_util.token - client_id: self.client_id - cache_tokens: self.token_util.token_cache - delegate_login: self.delegate login """ return { "token": self.token_util.token, "client_id": self.client_id, "cache_tokens": self.token_util.token_cache, "delegate_login": self._delegate_login } STATUS_REPOLL_SECONDS = 2 STATUS_REPOLL_MAX_CYCLES = 20 def upload_file_and_await_outline(self, file: typing.Union[FileStorage, str], include_relationships=True, status_repoll_seconds: int = None, repoll_max_cycles: int = None): """ Useful for testing """ if type(file) is str: content_type = mimetypes.guess_type(file, False)[0] # type: ignore with open(file, 'rb') as f: # type: ignore stream = io.BytesIO(f.read()) file = FileStorage( stream=stream, # type: ignore filename=file, # type: ignore content_type=content_type, content_length=stream.__sizeof__() ) if not status_repoll_seconds: status_repoll_seconds = self.STATUS_REPOLL_SECONDS if not repoll_max_cycles: repoll_max_cycles = self.STATUS_REPOLL_MAX_CYCLES story = self.story.story_post_file(file=file) self.await_outline(story.id) return self.story.story_id_get(story.id, include_outline=True, include_relationships=include_relationships) def await_outline(self, story_id, status_repoll_seconds: int = None, repoll_max_cycles: int = None): task_running = True repoll_cycle_count = 0 if not status_repoll_seconds: status_repoll_seconds = self.STATUS_REPOLL_SECONDS if not repoll_max_cycles: repoll_max_cycles = self.STATUS_REPOLL_MAX_CYCLES while task_running: status, status_code, _ = self.story.story_id_status_get_with_http_info(story_id) if status_code == 204: task_running = False elif status_code == 200: if status.status == "SUCCESS": task_running = False elif repoll_cycle_count >= repoll_max_cycles: raise presalytics.lib.exceptions.ApiError(message="Error occured while uploading file", status_code=500) else: logger.info("Story creation task still running. Rechecking status in {0} seconds".format(status_repoll_seconds)) time.sleep(status_repoll_seconds) repoll_cycle_count += 1 return self.story.story_id_outline_get(story_id)
Class variables
var STATUS_REPOLL_SECONDS
-
int(x=0) -> integer int(x, base=10) -> integer
Convert a number or string to an integer, or return 0 if no arguments are given. If x is a number, return x.int(). For floating point numbers, this truncates towards zero.
If x is not a number or if base is given, then x must be a string, bytes, or bytearray instance representing an integer literal in the given base. The literal can be preceded by '+' or '-' and be surrounded by whitespace. The base defaults to 10. Valid bases are 0 and 2-36. Base 0 means to interpret the base from the string as an integer literal.
>>> int('0b100', base=0) 4
var STATUS_REPOLL_MAX_CYCLES
-
int(x=0) -> integer int(x, base=10) -> integer
Convert a number or string to an integer, or return 0 if no arguments are given. If x is a number, return x.int(). For floating point numbers, this truncates towards zero.
If x is not a number or if base is given, then x must be a string, bytes, or bytearray instance representing an integer literal in the given base. The literal can be preceded by '+' or '-' and be surrounded by whitespace. The base defaults to 10. Valid bases are 0 and 2-36. Base 0 means to interpret the base from the string as an integer literal.
>>> int('0b100', base=0) 4
Methods
def login(self)
-
Triggers a an attempt to acquire an API token based on the the client configuration
Expand source code Browse git
def login(self): """ Triggers a an attempt to acquire an API token based on the the client configuration """ if self.direct_grant: token = self.oidc.token(username=self.username, password=self.password) else: token = self.oidc.token(username=self.username) self.token_util.process_token(token) return self.token_util.token
def refresh_token(self)
-
Obtains a new access token if the access token is expired. if refresh token is expired, this method prompt user to re-authenticate when
delegate_login
isFalse
or raise anInvalidTokenException
whendeletegate_login
is True.Expand source code Browse git
def refresh_token(self): """ Obtains a new access token if the access token is expired. if refresh token is expired, this method prompt user to re-authenticate when `delegate_login` is `False` or raise an `presalytics.lib.exceptions.InvalidTokenException` when `deletegate_login` is True. """ if self.token_util.is_api_access_token_expired(): if self.token_util.token.get('refresh_token', None) and self.client_secret: refresh_token = self.token_util.token["refresh_token"] token = self.oidc.refresh_token(refresh_token) self.token_util.process_token(token) logger.debug("Refresh token granted successfully.") else: if self.direct_grant: token = self.oidc.token(username=self.username, password=self.password) elif self.confidential_client: token = self.oidc.client_credentials_token() elif self._delegate_login: raise presalytics.lib.exceptions.ApiError("Unauthorized. Token has expired", status_code=401) else: token = self.oidc.token(username=self.username) self.token_util.process_token(token) if self.token_util.token_cache: self.token_util._put_token_file() return self.token_util.token
def get_auth_header(self)
-
Creates a JWT Bearer Authorization token header
Returns
A
dict
authorization crediential to be attached to an API requestExpand source code Browse git
def get_auth_header(self): """ Creates a JWT Bearer Authorization token header Returns ---------- A `dict` authorization crediential to be attached to an API request """ self.refresh_token() auth_header = { "Authorization": "Bearer " + self.token_util.token["access_token"] } return auth_header
def get_request_id_header(self)
-
Creates an 'X-Request-Id' token header for tracing requests through Presalytics API services. If deployed alongside the WSGI Microservice Middleware package, this method will pull the request id from the call stack.
Returns
A
dict
header representation with an 'X-Request-Id' key to be attached to an API requestExpand source code Browse git
def get_request_id_header(self): """ Creates an 'X-Request-Id' token header for tracing requests through Presalytics API services. If deployed alongside the [WSGI Microservice Middleware](https://github.com/presalytics/WSGI-Microservice-Middleware) package, this method will pull the request id from the call stack. Returns ---------- A `dict` header representation with an 'X-Request-Id' key to be attached to an API request """ current_request_id = wsgi_microservice_middleware.current_request_id() if not current_request_id: current_request_id = str(uuid4()) header = { "X-Request-Id": current_request_id } return header
def download_file(self, story_id, ooxml_automation_id, download_folder=None, filename=None, **kwargs)
-
Downloads an updated Ooxml Automation file and places the file in a designated folder
Parameters
presalytics.story
:str
- The id of the Presalytics Story API object that manages access to document
ooxml_automation_id
:str
- The id of the Presalytics API Ooxml Automation service object that you want to download
download_folder
:str
, optional- The filepath to the local directory that you want to download the file to. Defaults to the current working directory.
filename
:str
, optional- The name of the downloaded file. Defaults to the original filename the the object was created.
Expand source code Browse git
def download_file(self, story_id, ooxml_automation_id, download_folder=None, filename=None, **kwargs): """ Downloads an updated Ooxml Automation file and places the file in a designated folder Parameters --------- story : str The id of the Presalytics Story API object that manages access to document ooxml_automation_id : str The id of the Presalytics API Ooxml Automation service object that you want to download download_folder : str, optional The filepath to the local directory that you want to download the file to. Defaults to the current working directory. filename: str, optional The name of the downloaded file. Defaults to the original filename the the object was created. """ response, status, headers = self.story.story_id_file_ooxmlautomationid_get_with_http_info(story_id, ooxml_automation_id, _preload_content=False) if download_folder is None: download_folder = os.getcwd() if filename is None: cd_header = headers.get('Content-Disposition') _, params = cgi.parse_header(cd_header) filename = params["filename"] filepath = os.path.join(download_folder, filename) with open(filepath, 'wb') as f: f.write(response.data)
def get_client_info(self)
-
Convenience method returning information about this client to pass to downstream objects, e.g., components and new client instances
Returns
A
dictionary
containing
instances
values
:-
- token: self.token_util.token
- client_id: self.client_id
- cache_tokens: self.token_util.token_cache
- delegate_login: self.delegate login
Expand source code Browse git
def get_client_info(self): """ Convenience method returning information about this client to pass to downstream objects, e.g., components and new client instances Returns ---------- A dictionary containing instances values: - token: self.token_util.token - client_id: self.client_id - cache_tokens: self.token_util.token_cache - delegate_login: self.delegate login """ return { "token": self.token_util.token, "client_id": self.client_id, "cache_tokens": self.token_util.token_cache, "delegate_login": self._delegate_login }
def upload_file_and_await_outline(self, file, include_relationships=True, status_repoll_seconds=None, repoll_max_cycles=None)
-
Useful for testing
Expand source code Browse git
def upload_file_and_await_outline(self, file: typing.Union[FileStorage, str], include_relationships=True, status_repoll_seconds: int = None, repoll_max_cycles: int = None): """ Useful for testing """ if type(file) is str: content_type = mimetypes.guess_type(file, False)[0] # type: ignore with open(file, 'rb') as f: # type: ignore stream = io.BytesIO(f.read()) file = FileStorage( stream=stream, # type: ignore filename=file, # type: ignore content_type=content_type, content_length=stream.__sizeof__() ) if not status_repoll_seconds: status_repoll_seconds = self.STATUS_REPOLL_SECONDS if not repoll_max_cycles: repoll_max_cycles = self.STATUS_REPOLL_MAX_CYCLES story = self.story.story_post_file(file=file) self.await_outline(story.id) return self.story.story_id_get(story.id, include_outline=True, include_relationships=include_relationships)
def await_outline(self, story_id, status_repoll_seconds=None, repoll_max_cycles=None)
-
Expand source code Browse git
def await_outline(self, story_id, status_repoll_seconds: int = None, repoll_max_cycles: int = None): task_running = True repoll_cycle_count = 0 if not status_repoll_seconds: status_repoll_seconds = self.STATUS_REPOLL_SECONDS if not repoll_max_cycles: repoll_max_cycles = self.STATUS_REPOLL_MAX_CYCLES while task_running: status, status_code, _ = self.story.story_id_status_get_with_http_info(story_id) if status_code == 204: task_running = False elif status_code == 200: if status.status == "SUCCESS": task_running = False elif repoll_cycle_count >= repoll_max_cycles: raise presalytics.lib.exceptions.ApiError(message="Error occured while uploading file", status_code=500) else: logger.info("Story creation task still running. Rechecking status in {0} seconds".format(status_repoll_seconds)) time.sleep(status_repoll_seconds) repoll_cycle_count += 1 return self.story.story_id_outline_get(story_id)
class StoryOutline (info, pages, description, title, themes, plugins=None, story_id='empty', **kwargs)
-
A StoryOutline contains instructions for a
Renderer
(e.g.,Revealer
) to render an story into html.A story outline's
info
,pages
,widgets
, andthemes
are intended to be easily editable by both human users and machines via json serialization and deserialization, and the ecosystem of tools that can be used to edit json objects. Once a validStoryOutline
is built, it contains sufficient instructions for this library find the required components in the user's workspace and render theme.StoryOutlines are stored in the Presalytics API Story Service. The story service manages version history and user permissions for StoryOutlines. For more information about how outlines are used, please see the How It Works section of the website.
Attributes
outline_version
:str
- the version of that StoryOuline schema.
info
:Info
- Metadata about this StoryOutline
pages
:list
ofPage
- The pages that will be rendered in this story
themes
:list
ofTheme
- The themes that will underlie each
Page
in the StoryOutline title
:str
, optional- A title for the story
description
:str
, optional- A description of the story
story_id
:str
, optional- The Presalytics API Story Id. Automatically added once the story outline has been pushed to the server.
Expand source code Browse git
class StoryOutline(OutlineBase): """ A StoryOutline contains instructions for a `presalytics.story.components.Renderer` (e.g., `presalytics.story.revealer.Revealer`) to render an story into html. A story outline's `info`, `pages`, `widgets`, and `themes` are intended to be easily editable by both human users and machines via json serialization and deserialization, and the ecosystem of tools that can be used to edit json objects. Once a valid `StoryOutline` is built, it contains sufficient instructions for this library find the required components in the user's workspace and render theme. StoryOutlines are stored in the [Presalytics API Story Service](https://presalytics.io/docs/api-specifications/story/). The story service manages version history and user permissions for StoryOutlines. For more information about how outlines are used, please see the [How It Works](https://presalytics.io/docs/how-it-works/) section of the website. Attributes ---------- outline_version : str the version of that StoryOuline schema. info : presalytics.story.outline.Info Metadata about this StoryOutline pages : list of presalytics.story.outline.Page The pages that will be rendered in this story themes : list of presalytics.story.outline.Theme The themes that will underlie each `presalytics.story.outline.Page` in the StoryOutline title : str, optional A title for the story description: str, optional A description of the story story_id : str, optional The Presalytics API Story Id. Automatically added once the story outline has been pushed to the server. """ outline_version: str info: Info pages: typing.List[Page] description: str title: str themes: typing.List[Theme] story_id: str __required__ = [ 'outline_version', 'info', 'pages', 'title', ] def __init__(self, info, pages, description, title, themes, plugins=None, story_id="empty", **kwargs): super(StoryOutline, self).__init__(**kwargs) self.outline_version = get_current_spec_version() self.info = Info.deserialize(info) self.pages = [Page.deserialize(x) for x in pages] if description: self.description = description else: self.description = "" if title: self.title = title else: self.title = "" if themes: self.themes = [Theme.deserialize(x) for x in themes] else: self.themes = [] if plugins: self.plugins = [Plugin.deserialize(x) for x in plugins] else: self.plugins = [] self.story_id = story_id if not kwargs.get("validate", True): self.validate() def validate(self): super(StoryOutline, self).validate() jsonschema.validate(instance=self.to_dict(), schema=load_latest_schema())
Ancestors
- OutlineBase
- abc.ABC
Inherited members
class Renderer (story_outline, **kwargs)
-
Base class for objects that convert
StoryOutline
objects into html and rendering them over the webWith this class, users can push changes to their
StoryOutline
to the Presalytics API and web clients. Renderer class contains a couplemethods for syncing changes from component instances in theCONFIG
to the Presalytics API Story service.-
The
view
method allows users programattically view their stories at https://presalytics.io after changes are made -
The
manage
method takes users to to the story management interface, where users can share their work with other users, continue making edits or change story properties.
Parameters
story_outline
:StoryOutline
- The presalytics StoryOutline to be rendered and presented
Attributes
plugins
:list
ofdict
- Plugin data that transform to html
<script>
and<link>
tags through the rendering process site_host
:str
- The host of the website. Defaults to https://presalytics.io.
view_url
:str
, optional- The url to view the story. Unavailable if story outline has not been pushed to the Presalytics API story service
manage_url
:str
, optional- The url to view the story. Unavailable if story outline has not been pushed to the Presalytics API story service
Expand source code Browse git
class Renderer(ComponentBase): """ Base class for objects that convert `presalytics.story.outline.StoryOutline` objects into html and rendering them over the web With this class, users can push changes to their `presalytics.story.outline.StoryOutline` to the Presalytics API and web clients. Renderer class contains a couplemethods for syncing changes from component instances in the `presalytics.CONFIG` to the Presalytics API Story service. * The `view` method allows users programattically view their stories at https://presalytics.io after changes are made * The `manage` method takes users to to the story management interface, where users can share their work with other users, continue making edits or change story properties. Parameters ---------- story_outline : presalytics.story.outline.StoryOutline The presalytics StoryOutline to be rendered and presented Attributes ----------- plugins : list of dict Plugin data that transform to html `<script>` and `<link>` tags through the rendering process site_host : str The host of the website. Defaults to https://presalytics.io. view_url : str, optional The url to view the story. Unavailable if story outline has not been pushed to the Presalytics API story service manage_url : str, optional The url to view the story. Unavailable if story outline has not been pushed to the Presalytics API story service """ story_outline: 'StoryOutline' plugins: typing.List[typing.Dict] view_url: typing.Optional[str] manage_url: typing.Optional[str] __component_type__ = 'renderer' def __init__(self, story_outline : 'StoryOutline', **kwargs): super(Renderer, self).__init__(**kwargs) self.story_outline = story_outline try: self.site_host = presalytics.CONFIG["HOSTS"]["SITE"] except (KeyError, AttributeError): self.site_host = presalytics.lib.constants.SITE_HOST try: story_id = self.story_outline.story_id view_endpoint = presalytics.lib.constants.STORY_VIEW_URL.format(story_id) self.view_url = urllib.parse.urljoin(self.site_host, view_endpoint) manage_endpoint = presalytics.lib.constants.STORY_MANAGE_URL.format(story_id) self.manage_url = urllib.parse.urljoin(self.site_host, manage_endpoint) except (KeyError, AttributeError): self.view_url = None self.manage_url = None def render(self, **kwargs): """ Renders server-side (i.e., opens the story on https://presalytics.io) """ return self.view() def strip_unauthorized_scripts(self, body): """ Finds and removes unauthorized scripts from that the html document. For security reasons, content in `<script>` tags that has not been vetted by presalytics.io devops If you would like to get a tag included in the base library, raise an issue on [Github](https://github.com/presalytics/python-client/issues/new). We'd love to hear from you and learn about your use case, and will respond promptly to help. """ allowed_scripts = presalytics.lib.plugins.external.ApprovedExternalScripts().attr_dict.flatten().values() script_elements = body.findall(".//script") for ele in script_elements: remove_ele = True script_id = ele.get("id") if script_id in self.story_outline.allowed_ids: remove_ele = False link = ele.get("src") if link in allowed_scripts: remove_ele = False if remove_ele: ele.getparent().remove(ele) return body @classmethod def deserialize(cls, component: 'StoryOutline', **kwargs): """ Initializes the class from a `presalytics.story.outline.StoryOutline`. See __init___. """ return cls(component, **kwargs) def serialize(self) -> 'StoryOutline': """ Updates the story_outline Returns ----------- A refreshed `presalytics.story.outline.StoryOutline` """ self.update_outline_from_instances() return self.story_outline def update_outline_from_instances(self, sub_dict: typing.Dict = None): """ If a component instance for the widget is available in `presalytics.COMPONENTS`, this method find the instance and regenerates the component data so the latest data is available during the renering process. """ if not sub_dict: sub_dict = self.story_outline.to_dict() if sub_dict: for key, val in sub_dict.items(): if key in ["widgets", "themes", "pages"]: if isinstance(val, list): for list_item in val: if isinstance(list_item, dict): if "kind" in list_item: class_key = key.rstrip("s") + "." + list_item["kind"] klass = presalytics.COMPONENTS.get(class_key) if klass: if "name" in list_item: instance_key = class_key + "." + list_item["name"] inst = presalytics.COMPONENTS.get_instance(instance_key) if inst: self._set_outline_data_from_instance(inst) if isinstance(val, dict): if len(val.keys()) > 0: self.update_outline_from_instances(val) if isinstance(val, list): for list_item in val: if isinstance(list_item, dict): self.update_outline_from_instances(list_item) def get_component_implicit_plugins(self, sub_dict: typing.Dict = None): """ Retrieves plugin data from plugins attached to `presalytics.story.components` classes referenced in the `presalytics.story.outline.StoryOutline` """ if not sub_dict: sub_dict = self.story_outline.to_dict() if sub_dict: for key, val in sub_dict.items(): if key in ["widgets", "themes", "pages"]: if isinstance(val, list): for list_item in val: if isinstance(list_item, dict): if "kind" in list_item: class_key = key.rstrip("s") + "." + list_item["kind"] klass = presalytics.COMPONENTS.get(class_key) if klass: if len(klass.__plugins__) > 0: self.plugins.extend(klass.__plugins__) if isinstance(val, dict): if len(val.keys()) > 0: self.get_component_implicit_plugins(val) if isinstance(val, list): for list_item in val: if isinstance(list_item, dict): self.get_component_implicit_plugins(list_item) def _set_outline_data_from_instance(self, inst): if inst.__component_type__ == 'widget': self._set_widget_outline_data(inst) if inst.__component_type__ == 'page': self._set_page_outline_data(inst) if inst.__component_type__ == 'theme': self._set_theme_outline_data(inst) def _set_theme_outline_data(self, inst: 'ThemeBase'): theme_index = None for t in range(0, len(self.story_outline.themes)): if inst.name == self.story_outline.themes[t].name: theme_index = t if theme_index: break theme_outline = inst.serialize() if theme_index: self.story_outline.themes[theme_index] = theme_outline def _set_page_outline_data(self, inst: 'PageTemplateBase'): page_index = None for p in range(0, len(self.story_outline.pages)): if inst.name == self.story_outline.pages[p].name: page_index = p if page_index: break page_outline = inst.serialize() if page_index: self.story_outline.pages[page_index] = page_outline def _set_widget_outline_data(self, inst: 'WidgetBase'): widget_index: typing.Optional[int] page_index: typing.Optional[int] widget_index = None page_index = None for p in range(0, len(self.story_outline.pages)): for w in range(0, len(self.story_outline.pages[p].widgets)): widget = self.story_outline.pages[p].widgets[w] if widget.name == inst.name: page_index = p widget_index = w if page_index: break if page_index: break w_outline = inst.serialize() if isinstance(page_index, int) and isinstance(widget_index, int): # Causes 'unsupported target for assingment error` self.story_outline.pages[page_index].widgets[widget_index] = w_outline #type: ignore def update_story(self): """ Updates the StoryOutline and pushes those updates to the Presalytics API Story service """ self.update_outline_from_instances() client = presalytics.client.api.Client(**self.client_info) story = client.story.story_id_get(self.story_outline.story_id) story.outline = self.story_outline.dump() client.story.story_id_put(story.id, story) def view(self, update=False): """ Updates a story and opens it on the presalytics.io website Parameters ---------- update : bool Defaults to True. Indicates whether the StoryOutline should be updated prior to opening in the web browser """ if not self.view_url: message = "The outline has not been pushed to the Presalytics API yet, and therefore cannot be viewed via preslaytics.io" raise presalytics.lib.exceptions.InvalidConfigurationError(message=message) if update: self.update_story() webbrowser.open_new_tab(self.view_url) def manage(self, update=False): """ Updates a story and opens the management page on the presalytics.io website Parameters ---------- update : bool Defaults to True. Indicates whether the StoryOutline should be updated prior to opening in the web browser """ if not self.manage_url: message = "The outline has not been pushed to the Presalytics API yet, and therefore cannot be viewed via preslaytics.io" raise presalytics.lib.exceptions.InvalidConfigurationError(message=message) if update: self.update_story() webbrowser.open_new_tab(self.manage_url)
Ancestors
- ComponentBase
- abc.ABC
Subclasses
Static methods
def deserialize(component, **kwargs)
-
Initializes the class from a
StoryOutline
. See __init___.Expand source code Browse git
@classmethod def deserialize(cls, component: 'StoryOutline', **kwargs): """ Initializes the class from a `presalytics.story.outline.StoryOutline`. See __init___. """ return cls(component, **kwargs)
Methods
def render(self, **kwargs)
-
Renders server-side (i.e., opens the story on https://presalytics.io)
Expand source code Browse git
def render(self, **kwargs): """ Renders server-side (i.e., opens the story on https://presalytics.io) """ return self.view()
-
Finds and removes unauthorized scripts from that the html document. For security reasons, content in
<script>
tags that has not been vetted by presalytics.io devopsIf you would like to get a tag included in the base library, raise an issue on Github. We'd love to hear from you and learn about your use case, and will respond promptly to help.
Expand source code Browse git
def strip_unauthorized_scripts(self, body): """ Finds and removes unauthorized scripts from that the html document. For security reasons, content in `<script>` tags that has not been vetted by presalytics.io devops If you would like to get a tag included in the base library, raise an issue on [Github](https://github.com/presalytics/python-client/issues/new). We'd love to hear from you and learn about your use case, and will respond promptly to help. """ allowed_scripts = presalytics.lib.plugins.external.ApprovedExternalScripts().attr_dict.flatten().values() script_elements = body.findall(".//script") for ele in script_elements: remove_ele = True script_id = ele.get("id") if script_id in self.story_outline.allowed_ids: remove_ele = False link = ele.get("src") if link in allowed_scripts: remove_ele = False if remove_ele: ele.getparent().remove(ele) return body
def serialize(self)
-
Updates the story_outline
Returns
A refreshed
StoryOutline
Expand source code Browse git
def serialize(self) -> 'StoryOutline': """ Updates the story_outline Returns ----------- A refreshed `presalytics.story.outline.StoryOutline` """ self.update_outline_from_instances() return self.story_outline
def update_outline_from_instances(self, sub_dict=None)
-
If a component instance for the widget is available in
COMPONENTS
, this method find the instance and regenerates the component data so the latest data is available during the renering process.Expand source code Browse git
def update_outline_from_instances(self, sub_dict: typing.Dict = None): """ If a component instance for the widget is available in `presalytics.COMPONENTS`, this method find the instance and regenerates the component data so the latest data is available during the renering process. """ if not sub_dict: sub_dict = self.story_outline.to_dict() if sub_dict: for key, val in sub_dict.items(): if key in ["widgets", "themes", "pages"]: if isinstance(val, list): for list_item in val: if isinstance(list_item, dict): if "kind" in list_item: class_key = key.rstrip("s") + "." + list_item["kind"] klass = presalytics.COMPONENTS.get(class_key) if klass: if "name" in list_item: instance_key = class_key + "." + list_item["name"] inst = presalytics.COMPONENTS.get_instance(instance_key) if inst: self._set_outline_data_from_instance(inst) if isinstance(val, dict): if len(val.keys()) > 0: self.update_outline_from_instances(val) if isinstance(val, list): for list_item in val: if isinstance(list_item, dict): self.update_outline_from_instances(list_item)
def get_component_implicit_plugins(self, sub_dict=None)
-
Retrieves plugin data from plugins attached to
presalytics.story.components
classes referenced in theStoryOutline
Expand source code Browse git
def get_component_implicit_plugins(self, sub_dict: typing.Dict = None): """ Retrieves plugin data from plugins attached to `presalytics.story.components` classes referenced in the `presalytics.story.outline.StoryOutline` """ if not sub_dict: sub_dict = self.story_outline.to_dict() if sub_dict: for key, val in sub_dict.items(): if key in ["widgets", "themes", "pages"]: if isinstance(val, list): for list_item in val: if isinstance(list_item, dict): if "kind" in list_item: class_key = key.rstrip("s") + "." + list_item["kind"] klass = presalytics.COMPONENTS.get(class_key) if klass: if len(klass.__plugins__) > 0: self.plugins.extend(klass.__plugins__) if isinstance(val, dict): if len(val.keys()) > 0: self.get_component_implicit_plugins(val) if isinstance(val, list): for list_item in val: if isinstance(list_item, dict): self.get_component_implicit_plugins(list_item)
def update_story(self)
-
Updates the StoryOutline and pushes those updates to the Presalytics API Story service
Expand source code Browse git
def update_story(self): """ Updates the StoryOutline and pushes those updates to the Presalytics API Story service """ self.update_outline_from_instances() client = presalytics.client.api.Client(**self.client_info) story = client.story.story_id_get(self.story_outline.story_id) story.outline = self.story_outline.dump() client.story.story_id_put(story.id, story)
def view(self, update=False)
-
Updates a story and opens it on the presalytics.io website
Parameters
update
:bool
- Defaults to True. Indicates whether the StoryOutline should be updated prior to opening in the web browser
Expand source code Browse git
def view(self, update=False): """ Updates a story and opens it on the presalytics.io website Parameters ---------- update : bool Defaults to True. Indicates whether the StoryOutline should be updated prior to opening in the web browser """ if not self.view_url: message = "The outline has not been pushed to the Presalytics API yet, and therefore cannot be viewed via preslaytics.io" raise presalytics.lib.exceptions.InvalidConfigurationError(message=message) if update: self.update_story() webbrowser.open_new_tab(self.view_url)
def manage(self, update=False)
-
Updates a story and opens the management page on the presalytics.io website
Parameters
update
:bool
- Defaults to True. Indicates whether the StoryOutline should be updated prior to opening in the web browser
Expand source code Browse git
def manage(self, update=False): """ Updates a story and opens the management page on the presalytics.io website Parameters ---------- update : bool Defaults to True. Indicates whether the StoryOutline should be updated prior to opening in the web browser """ if not self.manage_url: message = "The outline has not been pushed to the Presalytics API yet, and therefore cannot be viewed via preslaytics.io" raise presalytics.lib.exceptions.InvalidConfigurationError(message=message) if update: self.update_story() webbrowser.open_new_tab(self.manage_url)
Inherited members
-
class Revealer (story_outline, pages=None, **kwargs)
-
Renders
StoryOutline
objects to reveal.js presentationsParameters
story_outline
:StoryOutline
- The presalytics StoryOutline to be rendered and presented
Attributes
base
:lxml.etree.Element
- An etree element containing the base html for each slide
plugins
:list
ofdict
- Plugin data that transform to html
<script>
and<link>
tags through the rendering process plugin_mgr
:PluginManager
- Sorts, validates, and renders plugins
Expand source code Browse git
class Revealer(presalytics.story.components.Renderer): """ Renders `presalytics.story.outline.StoryOutline` objects to [reveal.js](https://github.com/hakimel/reveal.js/) presentations Parameters ---------- story_outline : presalytics.story.outline.StoryOutline The presalytics StoryOutline to be rendered and presented Attributes ----------- base : lxml.etree.Element An etree element containing the base html for each slide plugins : list of dict Plugin data that transform to html `<script>` and `<link>` tags through the rendering process plugin_mgr : presalytics.lib.plugins.base.PluginManager Sorts, validates, and renders plugins """ base: lxml.etree.Element reveal_params: typing.Dict[str, typing.Any] __component_kind__ = 'revealer' def __init__( self, story_outline: 'StoryOutline', pages: typing.List[int] = None, **kwargs): super(Revealer, self).__init__(story_outline, **kwargs) logger.info("Initializing story render for {}".format(story_outline.title)) self.story_outline.validate() if isinstance(pages, int): pages = [pages] elif not pages: pages = [p for p in range(0, len(self.story_outline.pages))] elif not isinstance(pages, list) or not isinstance(pages[0], int): raise presalytics.lib.exceptions.InvalidArgumentException(message='"pages" must be a list of integers') if len([p for p in pages if p >= len(self.story_outline.pages)]) > 0: raise presalytics.lib.exceptions.InvalidArgumentException(message='"pages" can only contain integers lower than the number of pages in the story') self.pages_to_render = pages self.base = self._make_base() logger.info("Loading plugins") reveal_params = {} for key, val in kwargs.items(): if key in presalytics.lib.plugins.reveal.RevealConfigPlugin.default_config.keys(): reveal_params.update({key: val}) if len(self.pages_to_render) == 1: reveal_params.update({'controls': False}) # hide controls on single page story reveal_plugin_config = { 'kind': 'script', 'name': 'reveal', 'config': {"reveal_params": reveal_params} if len(reveal_params.keys()) > 0 else {} } overrides_config = { 'kind': 'style', 'name': 'local', 'config': { "css_file_id": "reveal_overrides" } } self.update_outline_from_instances() self.plugins = [reveal_plugin_config, overrides_config] self.get_component_implicit_plugins() outline_plugins = presalytics.lib.plugins.base.PluginManager.get_plugins_from_nested_dict(source_dict=self.story_outline.to_dict()) self.plugins.extend(outline_plugins) self.plugin_mgr = presalytics.lib.plugins.base.PluginManager(self.plugins) logger.info("Revealer initilized.") def _make_base(self): base = lxml.etree.Element("div", attrib={ "class": "reveal", }) lxml.etree.SubElement(base, "div", attrib={"class": "slides"}) try: story_id = self.story_outline.story_id base.attrib['data-story-id'] = story_id except Exception: logger.info("Revealer could not extract story_id from outline.") return base def get_meta_tags(self, body=tuple()): """ Security Note: If supplying a body, ensure that its already been stripped of unauthorized scripts. """ scripts = body.findall(".//script") srcs = [] for script in scripts: src = script.get("src") if src: root = urllib.parse.urlparse(src).netloc if root: srcs.append(root) allowed = ' '.join(set(srcs)) tags = [ '<meta charset="utf-8">', '<meta http-equiv="X-UA-Compatible" content="IE=edge">', '<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">', # """<meta http-equiv="Content-Security-Policy" content="default-src 'self' https://*.presalytics.io; script-src 'self' https://*.presalytics.io {0};">""".format(allowed) ] return tags def package_as_standalone(self): """ Render the story outline as a html document with only the reveal.js presentation as conent Returns ---------- A `str` containing a complete html document with the presentation """ pres = self.render() body = E.BODY() body.append(pres) body = self.strip_unauthorized_scripts(body) for scripts in self.plugin_mgr.get_scripts(): lxml_scripts = lxml.html.fragments_fromstring(scripts) for item in lxml_scripts: body.append(item) head = E.HEAD() for meta in self.get_meta_tags(body): lxml_meta = lxml.html.fragment_fromstring(meta) head.append(lxml_meta) for link in self.plugin_mgr.get_styles(): lxml_links = lxml.html.fragments_fromstring(link) for item in lxml_links: head.append(item) head = self.strip_unauthorized_scripts(head) html = E.HTML( head, body ) return lxml.html.tostring(html, pretty_print=True) # def render_plugin(self, plugin): # plugin_type = plugin['type'] # plugin_config = plugin['config'] # plugin_class = self.plugin_loader.load(plugin_type) # script_string = plugin_class().to_string(plugin_config) # return lxml.html.fragment_fromstring(script_string) def update_info(self): """ Updates story metadata """ info = self.story_outline.info info.date_modified = datetime.datetime.utcnow() def render(self): """ Creates a reveal.js presenation html fragement Returns: --------- A `str` html fragment containing a reveal.js presentation """ reveal_base = self.base for p in range(0, len(self.story_outline.pages)): if p in self.pages_to_render: page = self.story_outline.pages[p] slides_container = reveal_base[0] slide = lxml.etree.SubElement(slides_container, "section") page_html = self.render_page(page) slide_fragment = lxml.html.fragment_fromstring(page_html) slide.append(slide_fragment) return reveal_base def render_page(self, page: 'Page') -> str: """ Creates a reveal.js slide Returns ---------- A `str` html framgment of the page """ class_key = "page." + page.kind key = class_key + "." + page.name try: if presalytics.COMPONENTS.get_instance(key): page_instance = presalytics.COMPONENTS.get_instance(key) else: klass = presalytics.COMPONENTS.get(class_key) deserialize_method = getattr(klass, "deserialize", None) if callable(deserialize_method): page_instance = deserialize_method(page, client_info=self.client_info) else: message = 'Page component instance or class (kind) "{0}" unavailable in component registry'.format(key) raise presalytics.lib.exceptions.MissingConfigException(message) page_html = page_instance.render() except Exception as ex: logger.exception(ex) t, v, tb = sys.exc_info() if not presalytics.CONFIG.get("DEBUG", False): page_html = presalytics.lib.exceptions.RenderExceptionHandler(ex, "page", traceback=tb).render_exception() else: six.reraise(t, v, tb) return page_html def present(self, files_path=None, debug=True, port=8082, host='127.0.0.1'): """ Creates and opens the rendered story in the browser. Story files are served by a local flask server. Not for production use. Press Ctrl + C to close the server. Parameters ---------- files_path : str filepath to a local folder that will work as root folder for a local flask server. Defaults to the user's temporary files directory debug : str Defaults to True. Indicates whether the flask server should be started in debug mode. port : str The network port to serve the story onto. Defautls to 8082. host : str The host to for the local server. Typically either localhost or the default gateway. Defaults to 127.0.0.1 (localhost). """ logger.info("Building story rendering at http://{0}:{1}".format(host, port)) if not files_path: files_path = tempfile.gettempdir() logger.info("Buidling standalone package for local rendering.") html = self.package_as_standalone().decode('utf-8') id = presalytics.story.util.to_title_case(self.story_outline.title) if id == '': id = 'blank' server = presalytics.story.server.LocalServer(host=host, debug=debug, port=port, root_path=files_path) pkg_templates_dir = os.path.join(os.path.dirname(os.path.dirname(__file__)), "lib", "templates") shutil.copy(os.path.join(pkg_templates_dir, "favicon.ico"), os.path.join(files_path, "presalytics", "static")) html_file = os.path.join(files_path, "presalytics", "templates", id + '.html') with open(html_file, 'w') as file: file.write(html) address = "http://{}:{}/story/{}".format(host, port, id) logger.info("Opening browser tab...") presalytics.story.server.Browser(address).start() server.run()
Ancestors
- Renderer
- ComponentBase
- abc.ABC
Methods
-
Security Note: If supplying a body, ensure that its already been stripped of unauthorized scripts.
Expand source code Browse git
def get_meta_tags(self, body=tuple()): """ Security Note: If supplying a body, ensure that its already been stripped of unauthorized scripts. """ scripts = body.findall(".//script") srcs = [] for script in scripts: src = script.get("src") if src: root = urllib.parse.urlparse(src).netloc if root: srcs.append(root) allowed = ' '.join(set(srcs)) tags = [ '<meta charset="utf-8">', '<meta http-equiv="X-UA-Compatible" content="IE=edge">', '<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">', # """<meta http-equiv="Content-Security-Policy" content="default-src 'self' https://*.presalytics.io; script-src 'self' https://*.presalytics.io {0};">""".format(allowed) ] return tags
def package_as_standalone(self)
-
Render the story outline as a html document with only the reveal.js presentation as conent
Returns
A
str
containing a complete html document with the presentationExpand source code Browse git
def package_as_standalone(self): """ Render the story outline as a html document with only the reveal.js presentation as conent Returns ---------- A `str` containing a complete html document with the presentation """ pres = self.render() body = E.BODY() body.append(pres) body = self.strip_unauthorized_scripts(body) for scripts in self.plugin_mgr.get_scripts(): lxml_scripts = lxml.html.fragments_fromstring(scripts) for item in lxml_scripts: body.append(item) head = E.HEAD() for meta in self.get_meta_tags(body): lxml_meta = lxml.html.fragment_fromstring(meta) head.append(lxml_meta) for link in self.plugin_mgr.get_styles(): lxml_links = lxml.html.fragments_fromstring(link) for item in lxml_links: head.append(item) head = self.strip_unauthorized_scripts(head) html = E.HTML( head, body ) return lxml.html.tostring(html, pretty_print=True)
def update_info(self)
-
Updates story metadata
Expand source code Browse git
def update_info(self): """ Updates story metadata """ info = self.story_outline.info info.date_modified = datetime.datetime.utcnow()
def render(self)
-
Creates a reveal.js presenation html fragement
Returns:
A
str
html fragment containing a reveal.js presentationExpand source code Browse git
def render(self): """ Creates a reveal.js presenation html fragement Returns: --------- A `str` html fragment containing a reveal.js presentation """ reveal_base = self.base for p in range(0, len(self.story_outline.pages)): if p in self.pages_to_render: page = self.story_outline.pages[p] slides_container = reveal_base[0] slide = lxml.etree.SubElement(slides_container, "section") page_html = self.render_page(page) slide_fragment = lxml.html.fragment_fromstring(page_html) slide.append(slide_fragment) return reveal_base
def render_page(self, page)
-
Creates a reveal.js slide
Returns
A
str
html framgment of the pageExpand source code Browse git
def render_page(self, page: 'Page') -> str: """ Creates a reveal.js slide Returns ---------- A `str` html framgment of the page """ class_key = "page." + page.kind key = class_key + "." + page.name try: if presalytics.COMPONENTS.get_instance(key): page_instance = presalytics.COMPONENTS.get_instance(key) else: klass = presalytics.COMPONENTS.get(class_key) deserialize_method = getattr(klass, "deserialize", None) if callable(deserialize_method): page_instance = deserialize_method(page, client_info=self.client_info) else: message = 'Page component instance or class (kind) "{0}" unavailable in component registry'.format(key) raise presalytics.lib.exceptions.MissingConfigException(message) page_html = page_instance.render() except Exception as ex: logger.exception(ex) t, v, tb = sys.exc_info() if not presalytics.CONFIG.get("DEBUG", False): page_html = presalytics.lib.exceptions.RenderExceptionHandler(ex, "page", traceback=tb).render_exception() else: six.reraise(t, v, tb) return page_html
def present(self, files_path=None, debug=True, port=8082, host='127.0.0.1')
-
Creates and opens the rendered story in the browser. Story files are served by a local flask server. Not for production use. Press Ctrl + C to close the server.
Parameters
files_path
:str
- filepath to a local folder that will work as root folder for a local flask server. Defaults to the user's temporary files directory
debug
:str
- Defaults to True. Indicates whether the flask server should be started in debug mode.
port
:str
- The network port to serve the story onto. Defautls to 8082.
host
:str
- The host to for the local server. Typically either localhost or the default gateway. Defaults to 127.0.0.1 (localhost).
Expand source code Browse git
def present(self, files_path=None, debug=True, port=8082, host='127.0.0.1'): """ Creates and opens the rendered story in the browser. Story files are served by a local flask server. Not for production use. Press Ctrl + C to close the server. Parameters ---------- files_path : str filepath to a local folder that will work as root folder for a local flask server. Defaults to the user's temporary files directory debug : str Defaults to True. Indicates whether the flask server should be started in debug mode. port : str The network port to serve the story onto. Defautls to 8082. host : str The host to for the local server. Typically either localhost or the default gateway. Defaults to 127.0.0.1 (localhost). """ logger.info("Building story rendering at http://{0}:{1}".format(host, port)) if not files_path: files_path = tempfile.gettempdir() logger.info("Buidling standalone package for local rendering.") html = self.package_as_standalone().decode('utf-8') id = presalytics.story.util.to_title_case(self.story_outline.title) if id == '': id = 'blank' server = presalytics.story.server.LocalServer(host=host, debug=debug, port=port, root_path=files_path) pkg_templates_dir = os.path.join(os.path.dirname(os.path.dirname(__file__)), "lib", "templates") shutil.copy(os.path.join(pkg_templates_dir, "favicon.ico"), os.path.join(files_path, "presalytics", "static")) html_file = os.path.join(files_path, "presalytics", "templates", id + '.html') with open(html_file, 'w') as file: file.write(html) address = "http://{}:{}/story/{}".format(host, port, id) logger.info("Opening browser tab...") presalytics.story.server.Browser(address).start() server.run()
Inherited members
class MatplotlibFigure (figure, name, *args, **kwargs)
-
A
Widget
for renderingmatplotlib.pyplot.Figure
instances in storiesThis class acts as wrapper class for matplotlib figures, allowing their packaging into
Widget
objects and serialization to json. At render-time, the figure is converted to a d3.js object via the mpld3 package.Parameters
figure
:matplotlib.pyplot.Figure
- the figure to create the widget from
name
:str
- the name of widget.
Must be unique within
COMPONENTS
Attributes
figure_dict
:dict
- A
dict
containing json-serializable data for reconstituting thematplotlib.pyplot.Figure
object. figure_id
:str
- A unique identifier used to render the that figure into a d3.js object
Expand source code Browse git
class MatplotlibFigure(presalytics.story.components.WidgetBase): """ A `Widget` for rendering `matplotlib.pyplot.Figure` instances in stories This class acts as wrapper class for matplotlib figures, allowing their packaging into `presalytics.story.outline.Widget` objects and serialization to json. At render-time, the figure is converted to a d3.js object via the [mpld3](https://mpld3.github.io/) package. Parameters ---------- figure : `matplotlib.pyplot.Figure` the figure to create the widget from name : str the name of widget. Must be unique within `presalytics.COMPONENTS` Attributes ---------- figure_dict : dict A `dict` containing json-serializable data for reconstituting the `matplotlib.pyplot.Figure` object. figure_id : str A unique identifier used to render the that figure into a d3.js object """ __component_kind__ = 'matplotlib-figure' additional_properties: typing.Dict def __init__(self, figure: 'Figure', name: str, *args, **kwargs): super().__init__(self, *args, **kwargs) self.fig = figure self.name = name self.figure_dict = None if self.fig: self.figure_dict = mpld3.fig_to_dict(figure) else: figure_dict = kwargs.pop("figure_dict", None) if figure_dict: self.figure_dict = figure_dict if self.figure_dict is None: message = "MatplotlibFigure requires a figure_dict attribute. Please supply either a valid figure_dict or matplotlib.pyplot.Figure object to __init__" raise presalytics.lib.exceptions.ValidationError(message) self.figure_id = self.figure_dict["id"] self.additional_properties = {} for key, val in kwargs.items(): self.additional_properties[key] = val self.outline_widget = self.serialize() def to_html(self): return '<div id="{0}" class="mpld3"></div>'.format(self.figure_id) @classmethod def deserialize(cls, outline, **kwargs): figure_dict = outline.data.get("figure_dict") kwargs.update(**outline.additional_properties) return cls(None, outline.name, figure_dict=figure_dict, **outline.additional_properties) def serialize(self, **kwargs): data = { 'figure_dict': self.figure_dict, 'id': self.figure_id } plugin_obj = presalytics.story.outline.Plugin( name='mpld3', kind='script', config=data ) return presalytics.story.outline.Widget( name=self.name, kind=self.__component_kind__, plugins=[plugin_obj.to_dict()], data=data, additional_properties=self.additional_properties )
Ancestors
- WidgetBase
- ComponentBase
- abc.ABC
Subclasses
Inherited members
class MatplotlibResponsiveFigure (figure, name, story_id='empty', *args, **kwargs)
-
A
Widget
for renderingmatplotlib.pyplot.Figure
instances in storiesThis class acts as wrapper class for matplotlib figures, allowing their packaging into
Widget
objects and serialization to json. At render-time, the figure is converted to a d3.js object via the mpld3 package.This class also depends on the static plugin
Parameters
figure
:matplotlib.pyplot.Figure
- the figure to create the widget from
name
:str
- the name of widget.
Must be unique within
COMPONENTS
story_id
:str
, optional- The story of thet
Attributes
figure_dict
:dict
- A
dict
containing json-serializable data for reconstituting thematplotlib.pyplot.Figure
object. figure_id
:str
- A unique identifier used to render the that figure into a d3.js object
Expand source code Browse git
class MatplotlibResponsiveFigure(MatplotlibFigure): """ A `Widget` for rendering `matplotlib.pyplot.Figure` instances in stories This class acts as wrapper class for matplotlib figures, allowing their packaging into `presalytics.story.outline.Widget` objects and serialization to json. At render-time, the figure is converted to a d3.js object via the [mpld3](https://mpld3.github.io/) package. This class also depends on the static plugin Parameters ---------- figure : `matplotlib.pyplot.Figure` the figure to create the widget from name : str the name of widget. Must be unique within `presalytics.COMPONENTS` story_id : str, optional The story of thet Attributes ---------- figure_dict : dict A `dict` containing json-serializable data for reconstituting the `matplotlib.pyplot.Figure` object. figure_id : str A unique identifier used to render the that figure into a d3.js object """ additional_properties: typing.Dict __component_kind__ = 'matplotlib-responsive' __plugins__ = [ { 'name': 'external_scripts', 'kind': 'script', 'config': { 'approved_scripts_key': 'jquery' } }, { 'name': 'external_scripts', 'kind': 'script', 'config': { 'approved_scripts_key': 'mpl-responsive' } }, { 'name': 'external_links', 'kind': 'style', 'config': { 'approved_styles_key': 'preloaders' } } ] #type: ignore def __init__(self, figure: 'Figure', name: str, story_id: str = "empty", *args, **kwargs): self.story_id = story_id super(MatplotlibResponsiveFigure, self).__init__(figure, name, *args, **kwargs) self.story_host = self.get_client(delegate_login=True).story.api_client.configuration.host def to_html(self): if not self.story_id: message = "This object requires a valid story_id to render." raise presalytics.lib.exceptions.MissingConfigException(message=message) html = self.create_container() return html def create_container(self, **kwargs): """ Wraps the Matplotlib Figure in a SVG endpoint load via `<iframe>` that will be rendered inside of a story and rescaled to give repsonsive effect """ params = { "site_host": self.get_client(delegate_login=True).site_host, "figure_id": self.figure_id, "story_id": self.story_id } source_url = "{site_host}/story/matplotlib-figure/{story_id}/{figure_id}".format(**params) empty_parent_div = lxml.html.Element("div", { 'class': 'empty-parent bg-light matplotlib-responsive-container', 'style': 'height: 100%; width: 100%, display: block; text-align: left;' }) frame = lxml.html.Element("iframe", { 'src': source_url, 'frameborder': "0", 'scrolling': "no", 'class': 'matplotlib-responsive-frame', 'style': 'max-height: none; max-width: none;' }) empty_parent_div.append(frame) return lxml.html.tostring(empty_parent_div).decode('utf-8') @classmethod def deserialize(cls, outline, **kwargs): figure_dict = outline.data.get("figure_dict") story_id = outline.data.get("story_id", None) kwargs.update(**outline.additional_properties) return cls(None, outline.name, story_id, figure_dict=figure_dict, **kwargs) def serialize(self, **kwargs): data = { 'figure_dict': self.figure_dict, 'id': self.figure_id, 'story_id': self.story_id } return presalytics.story.outline.Widget( name=self.name, kind=self.__component_kind__, data=data, additional_properties=self.additional_properties ) def standalone_html(self) -> str: SIMPLE_HTML = jinja2.Template("""<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <style> {{ extra_css }} </style> </head> <body> <script type="text/javascript" src="{{ d3_url }}"></script> <script type="text/javascript" src="{{ mpld3_url }}"></script> <div id="{{ figid }}"></div> <script type="text/javascript"> mpld3.draw_figure("{{ figid }}", {{ figure_json }}); </script> </body> </html>""") figure_json = json.dumps(self.figure_dict, cls=presalytics.lib.plugins.matplotlib.Mpld3NumpyToJson) context = { "d3_url": presalytics.lib.plugins.external.ApprovedExternalScripts().attr_dict.flatten().get('d3v3'), "mpld3_url": presalytics.lib.plugins.external.ApprovedExternalScripts().attr_dict.flatten().get('mpld3'), "figid": self.figure_id, "figure_json": figure_json } return SIMPLE_HTML.render(**context)
Ancestors
- MatplotlibFigure
- WidgetBase
- ComponentBase
- abc.ABC
Methods
def create_container(self, **kwargs)
-
Wraps the Matplotlib Figure in a SVG endpoint load via
<iframe>
that will be rendered inside of a story and rescaled to give repsonsive effectExpand source code Browse git
def create_container(self, **kwargs): """ Wraps the Matplotlib Figure in a SVG endpoint load via `<iframe>` that will be rendered inside of a story and rescaled to give repsonsive effect """ params = { "site_host": self.get_client(delegate_login=True).site_host, "figure_id": self.figure_id, "story_id": self.story_id } source_url = "{site_host}/story/matplotlib-figure/{story_id}/{figure_id}".format(**params) empty_parent_div = lxml.html.Element("div", { 'class': 'empty-parent bg-light matplotlib-responsive-container', 'style': 'height: 100%; width: 100%, display: block; text-align: left;' }) frame = lxml.html.Element("iframe", { 'src': source_url, 'frameborder': "0", 'scrolling': "no", 'class': 'matplotlib-responsive-frame', 'style': 'max-height: none; max-width: none;' }) empty_parent_div.append(frame) return lxml.html.tostring(empty_parent_div).decode('utf-8')
def standalone_html(self)
-
Expand source code Browse git
def standalone_html(self) -> str: SIMPLE_HTML = jinja2.Template("""<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <style> {{ extra_css }} </style> </head> <body> <script type="text/javascript" src="{{ d3_url }}"></script> <script type="text/javascript" src="{{ mpld3_url }}"></script> <div id="{{ figid }}"></div> <script type="text/javascript"> mpld3.draw_figure("{{ figid }}", {{ figure_json }}); </script> </body> </html>""") figure_json = json.dumps(self.figure_dict, cls=presalytics.lib.plugins.matplotlib.Mpld3NumpyToJson) context = { "d3_url": presalytics.lib.plugins.external.ApprovedExternalScripts().attr_dict.flatten().get('d3v3'), "mpld3_url": presalytics.lib.plugins.external.ApprovedExternalScripts().attr_dict.flatten().get('mpld3'), "figid": self.figure_id, "figure_json": figure_json } return SIMPLE_HTML.render(**context)
Inherited members
class OoxmlFileWidget (filename, name=None, story_id=None, object_ooxml_id=None, endpoint_map=None, object_name=None, previous_ooxml_version={}, file_last_modified=None, document_ooxml_id=None, **kwargs)
-
Builds a
widget
from a Presentation or Spreadsheet documentThis class interacts with the Presalytics API to extract SVG objects from Presentation and spreadsheet documents, identify them, and render them into a story. The file is uploaded to Presalytics API Ooxml Automation service, which then processes the file and scans for objects in the file's object tree (As seen in the 'Selection Pane' in PowerPoint) for objects matching the 'object_name'. When rendered, this widget retrieves an SVG of the identified object for rendering within the story.
Please note that the Presalytics API Ooxml Automation object will be created overwritten each time this widget is initialized, and replaced within the corresponding
StoryOutline
. For in-place editing of widgets Ooxml Automation objects that are already bound to theStory
, please seeOoxmlEditorWidget
Parameters
filename
:str
- The local filepath a presentation or spreadsheet file containing the object to be rendered
name
:str
, optional- The widget name.
If not provided, attribute will be set as the
object_name
orfilename
story_id
:str
, optional- The the id of the story in the Presalytics API Story service. If not provided, a new story will be created. Do not supply if this object has not yet been created.
object_ooxml_id
:str
, optional- The identifier of the Ooxml Automation service object bound the Story. Do not supply if this object has not yet been created.
endpoint_map
:OoxmlEndpointMap
, optional- Reference to the Presalytics API Ooxml Automation service endpoint and object type that for the object of interest
object_name
:str
, optional- The name of the object in the file's object tree the will be rendered
previous_ooxml_version
:str
, optional- The id Ooxml Automation service document object that was previously used to
occupy this widget in the
StoryOutline
file_last_modified
:str
, optional- The "last modified" date for the file at the
filename
path. Used to ascertain whether file has been updated since the last time the widget was initialized, and correspondingly, whether the widget should be updated in theStoryOutline
document_ooxml_id
:str
, optional- The identifier for the parent "Document" object in the Ooxml Automation service for the object
idenitifiable by a combinatation of
object_ooxml_id
andendpoint_map
.
Expand source code Browse git
class OoxmlFileWidget(OoxmlWidgetBase): """ Builds a `widget` from a Presentation or Spreadsheet document This class interacts with the Presalytics API to extract SVG objects from Presentation and spreadsheet documents, identify them, and render them into a story. The file is uploaded to Presalytics API Ooxml Automation service, which then processes the file and scans for objects in the file's object tree (As seen in the 'Selection Pane' in PowerPoint) for objects matching the 'object_name'. When rendered, this widget retrieves an SVG of the identified object for rendering within the story. Please note that the Presalytics API Ooxml Automation object will be created overwritten each time this widget is initialized, and replaced within the corresponding `presalytics.story.outline.StoryOutline`. For in-place editing of widgets Ooxml Automation objects that are already bound to the `Story`, please see `presalytics.lib.widgets.ooxml_editors.OoxmlEditorWidget` Parameters ---------- filename : str The local filepath a presentation or spreadsheet file containing the object to be rendered name : str, optional The widget name. If not provided, attribute will be set as the `object_name` or `filename` story_id : str, optional The the id of the story in the Presalytics API Story service. If not provided, a new story will be created. Do not supply if this object has not yet been created. object_ooxml_id : str, optional The identifier of the Ooxml Automation service object bound the Story. Do not supply if this object has not yet been created. endpoint_map : presalytics.lib.widgets.ooxml.OoxmlEndpointMap, optional Reference to the Presalytics API Ooxml Automation service endpoint and object type that for the object of interest object_name : str, optional The name of the object in the file's object tree the will be rendered previous_ooxml_version : str, optional The id Ooxml Automation service document object that was previously used to occupy this widget in the `presalytics.story.outline.StoryOutline` file_last_modified : str, optional The "last modified" date for the file at the `filename` path. Used to ascertain whether file has been updated since the last time the widget was initialized, and correspondingly, whether the widget should be updated in the `presalytics.story.outline.StoryOutline` document_ooxml_id : str, optional The identifier for the parent "Document" object in the Ooxml Automation service for the object idenitifiable by a combinatation of `object_ooxml_id` and `endpoint_map`. """ object_name: typing.Optional[str] ooxml_id: str file_last_modified: datetime.datetime previous_ooxml_version: typing.Dict[str, str] __component_kind__ = 'ooxml-file-object' def __init__(self, filename, name=None, story_id=None, object_ooxml_id=None, endpoint_map=None, object_name=None, previous_ooxml_version={}, file_last_modified=None, document_ooxml_id=None, **kwargs): if object_name: self.object_name = object_name else: self.object_name = None if not name: if self.object_name: name = self.object_name else: name = filename super(OoxmlFileWidget, self).__init__(name, story_id, object_ooxml_id, endpoint_map, **kwargs) self.filename = os.path.basename(filename) if not self.endpoint_map: if filename.split(".")[-1] in ["pptx", "ppt"]: self.endpoint_map = OoxmlEndpointMap(OoxmlEndpointMap.SLIDE) self.previous_ooxml_version = previous_ooxml_version if file_last_modified: self.file_last_modified = file_last_modified.replace(tzinfo=datetime.timezone.utc) else: self.file_last_modified = datetime.datetime.utcnow() self.document_ooxml_id = document_ooxml_id self.update() self.svg_html = self.create_container(**kwargs) def update(self): """ If the file is available locally, this renders that updated file and pushes the updated rendering data to the server """ story: 'ApiStory' page: 'Page' widget: 'Widget' search_paths = list(set(presalytics.autodiscover_paths)) if os.getcwd() not in search_paths: search_paths.append(os.getcwd()) for path in search_paths: fpath = os.path.join(path, self.filename) if os.path.exists(fpath): # update only the file has been modified sine last time this_file_last_modified = datetime.datetime.utcfromtimestamp(os.path.getmtime(fpath)).astimezone(tz=datetime.timezone.utc) if self.file_last_modified is None or self.file_last_modified <= this_file_last_modified: client = self.get_client() story, status, headers = client.story.story_id_file_post_with_http_info(self.story_id, file=fpath, replace_existing=True, obsolete_id=self.document_ooxml_id) if status >= 299: raise presalytics.lib.exceptions.ApiError() self.previous_ooxml_version = { "document_ooxml_id": self.document_ooxml_id, "object_ooxml_id": self.object_ooxml_id } new_outline = presalytics.story.outline.StoryOutline.load(story.outline) for page in new_outline.pages: found = False for widget in page.widgets: if widget.name == self.object_name: self.document_ooxml_id = widget.data["document_ooxml_id"] found = True break if found: break if not found: message = "Unable to find widget object name {0} in new story outline. Has this widget been deleted?".format(self.object_name) raise presalytics.lib.exceptions.ValidationError(message) # Get object tree, compare to object name child_tree = client.ooxml_automation.documents_childobjects_get_id(self.document_ooxml_id) target_dto = None try: target_dto = next(x for x in child_tree if x.entity_name == self.object_name) except StopIteration: pass # if name not in object or _object_name is none, get first item of type in end_point id if not target_dto: try: target_dto = next(x for x in child_tree if x.object_type.split(".")[1] == self.endpoint_map.endpoint_id) except StopIteration: message = "Child tree of document {0} does not have a child object of type {1} or name {2}.".format(self.ooxml_id, self.endpoint_map.endpoint_id, self.object_name) raise presalytics.lib.exceptions.InvalidConfigurationError(message) # set widget parameters for recreation server-side (without file) self.object_ooxml_id = target_dto.entity_id self.file_last_modified = presalytics.lib.util.roundup_date_modified(this_file_last_modified) @classmethod def deserialize(cls, component, **kwargs): init_args = { "filename": component.data["filename"], "endpoint_map": OoxmlEndpointMap(component.data["endpoint_id"]), "object_name": component.data["object_name"], "name": component.name, } if "document_ooxml_id" in component.data: init_args.update( { "document_ooxml_id": component.data["document_ooxml_id"] } ) if "object_ooxml_id" in component.data: init_args.update( { "object_ooxml_id": component.data["object_ooxml_id"] } ) if "file_last_modified" in component.data: init_args.update( { "file_last_modified": dateutil.parser.parse(component.data["file_last_modified"]).replace(tzinfo=datetime.timezone.utc) } ) if "previous_ooxml_version" in component.data: init_args.update( { "previous_ooxml_version": component.data["previous_ooxml_version"] } ) if "story_id" in component.data: init_args.update( { "story_id": component.data["story_id"] } ) if len(kwargs.keys()) > 0: init_args.update(kwargs) return cls(**init_args) def serialize(self): self.update() data = { "filename": self.filename, "object_name": self.object_name, "endpoint_id": self.endpoint_map.endpoint_id, "document_ooxml_id": self.document_ooxml_id, "object_ooxml_id": self.object_ooxml_id, "story_id": self.story_id } if self.file_last_modified: data.update( { "file_last_modified": self.file_last_modified.isoformat() } ) if self.previous_ooxml_version: data.update( { "previous_ooxml_version": self.previous_ooxml_version } ) widget = presalytics.story.outline.Widget( name=self.name, kind=self.__component_kind__, data=data, plugins=None ) return widget
Ancestors
- OoxmlWidgetBase
- WidgetBase
- ComponentBase
- abc.ABC
Methods
def update(self)
-
If the file is available locally, this renders that updated file and pushes the updated rendering data to the server
Expand source code Browse git
def update(self): """ If the file is available locally, this renders that updated file and pushes the updated rendering data to the server """ story: 'ApiStory' page: 'Page' widget: 'Widget' search_paths = list(set(presalytics.autodiscover_paths)) if os.getcwd() not in search_paths: search_paths.append(os.getcwd()) for path in search_paths: fpath = os.path.join(path, self.filename) if os.path.exists(fpath): # update only the file has been modified sine last time this_file_last_modified = datetime.datetime.utcfromtimestamp(os.path.getmtime(fpath)).astimezone(tz=datetime.timezone.utc) if self.file_last_modified is None or self.file_last_modified <= this_file_last_modified: client = self.get_client() story, status, headers = client.story.story_id_file_post_with_http_info(self.story_id, file=fpath, replace_existing=True, obsolete_id=self.document_ooxml_id) if status >= 299: raise presalytics.lib.exceptions.ApiError() self.previous_ooxml_version = { "document_ooxml_id": self.document_ooxml_id, "object_ooxml_id": self.object_ooxml_id } new_outline = presalytics.story.outline.StoryOutline.load(story.outline) for page in new_outline.pages: found = False for widget in page.widgets: if widget.name == self.object_name: self.document_ooxml_id = widget.data["document_ooxml_id"] found = True break if found: break if not found: message = "Unable to find widget object name {0} in new story outline. Has this widget been deleted?".format(self.object_name) raise presalytics.lib.exceptions.ValidationError(message) # Get object tree, compare to object name child_tree = client.ooxml_automation.documents_childobjects_get_id(self.document_ooxml_id) target_dto = None try: target_dto = next(x for x in child_tree if x.entity_name == self.object_name) except StopIteration: pass # if name not in object or _object_name is none, get first item of type in end_point id if not target_dto: try: target_dto = next(x for x in child_tree if x.object_type.split(".")[1] == self.endpoint_map.endpoint_id) except StopIteration: message = "Child tree of document {0} does not have a child object of type {1} or name {2}.".format(self.ooxml_id, self.endpoint_map.endpoint_id, self.object_name) raise presalytics.lib.exceptions.InvalidConfigurationError(message) # set widget parameters for recreation server-side (without file) self.object_ooxml_id = target_dto.entity_id self.file_last_modified = presalytics.lib.util.roundup_date_modified(this_file_last_modified)
Inherited members
class OoxmlEndpointMap (endpoint_id, baseurl=None)
-
Mapping class that bridges Presalytics API Ooxml Automation service endpoints and component class that consume those endpoints (typically subclasses of
OoxmlWidgetBase
)The classmethods on this class are conveninece methods to help users quickly inform their widget which endpoint their of a Ooxml Document their targets.
Instance methods on this class are used by widget to generate urls and lookup against object tree for target objects.
Parameters
endpoint_id
:str
- A unique string for identifying the object_type related to the enpoint
baseurl
:str
- For developer use. Allows this to generate urls for non-standard instances of the Ooxml Automation service. Defaults to https://api.presalytics.io/ooxml-automation/
Attributes
root_url
:str
- the home url for the class instance.
Typically
<http://api.presalytics.io/ooxml-automation/{object_type}
> OBJECT_TYPE_MAP
:str
- A mapping table for object_types and object tree lookup keys
Expand source code Browse git
class OoxmlEndpointMap(object): """ Mapping class that bridges Presalytics API Ooxml Automation service endpoints and component class that consume those endpoints (typically subclasses of `presalytics.lib.widgets.ooxml.OoxmlWidgetBase`) The classmethods on this class are conveninece methods to help users quickly inform their widget which endpoint their of a Ooxml Document their targets. Instance methods on this class are used by widget to generate urls and lookup against object tree for target objects. Parameters ---------- endpoint_id : str A unique string for identifying the object_type related to the enpoint baseurl : str For developer use. Allows this to generate urls for non-standard instances of the Ooxml Automation service. Defaults to https://api.presalytics.io/ooxml-automation/ Attributes ---------- root_url : str the home url for the class instance. Typically `http://api.presalytics.io/ooxml-automation/{object_type}` OBJECT_TYPE_MAP : str A mapping table for object_types and object tree lookup keys """ _BASE_URL = "https://api.presalytics.io/ooxml-automation" _CHART = "Charts" _CONNECTION_SHAPE = "ConnectionShapes" _DOCUMENT = "Documents" _GROUP = "Groups" _IMAGE = "Images" _SHAPE = "Shapes" _SHAPETREE = "ShapeTrees" _SLIDE = "Slides" _TABLE = "Tables" _THEME = "Themes" def __init__(self, endpoint_id, baseurl: str = None): if endpoint_id not in OoxmlEndpointMap.__dict__.values(): raise presalytics.lib.exceptions.ValidationError("{0} is not a valid endpoint ID".format(endpoint_id)) self.endpoint_id = endpoint_id if not baseurl: self.baseurl = OoxmlEndpointMap._BASE_URL custom_hosts = presalytics.CONFIG.get("HOSTS", None) if custom_hosts: ooxml_host = custom_hosts.get("OOXML_AUTOMATION", None) if ooxml_host: self.baseurl = ooxml_host else: self.baseurl = baseurl self.root_url = posixpath.join(self.baseurl, self.endpoint_id) self.OBJECT_TYPE_MAP = self._build_object_type_map() def _build_object_type_map(self): return { "Chart": [ OoxmlEndpointMap._CHART, ], "Slide": [ OoxmlEndpointMap._GROUP, OoxmlEndpointMap._SHAPE, OoxmlEndpointMap._SHAPETREE, OoxmlEndpointMap._CONNECTION_SHAPE, OoxmlEndpointMap._SLIDE ], "Table": [ OoxmlEndpointMap._TABLE ], "Theme": [ OoxmlEndpointMap._THEME ], "Shared": [ OoxmlEndpointMap._IMAGE ], "EMPTY": [ OoxmlEndpointMap._DOCUMENT ] } def get_object_type(self): """ Returns the Ooxml Automation service object type for this endpoint """ for key, val in self.OBJECT_TYPE_MAP.items(): for test_ep in val: if test_ep == self.endpoint_id: if key == "EMPTY": return self.endpoint_id else: return "{0}.{1}".format(key, self.endpoint_id) message = "Invalid EndpointMap configuration: {0} is not in OBJECT_TYPE_MAP".format(self.endpoint_id) raise presalytics.lib.exceptions.ValidationError(message) def get_id_url(self, id): """ Returns a url to pull metadata from this Ooxml Automation service endpoint """ return posixpath.join(self.root_url, id) def get_svg_url(self, id): """ Returns a url to download an svg from this Ooxml Automation service endpoint """ return posixpath.join(self.root_url, "Svg", id) def get_xml_url(self, id): """ Returns a url to pull the Open Office Xml from this Ooxml Automation service endpoint """ return posixpath.join(self.root_url, "OpenOfficeXml", id) @classmethod def connection_shape(cls, baseurl=None): """ Factory method to create an `presalytics.lib.widgets.ooxml.OoxmlEndpointMap` instance targeting ConnectionShape objects """ return cls(OoxmlEndpointMap._CONNECTION_SHAPE, baseurl) @classmethod def chart(cls, baseurl=None): """ Factory method to create an `presalytics.lib.widgets.ooxml.OoxmlEndpointMap` instance targeting Chart objects """ return cls(OoxmlEndpointMap._CHART, baseurl) @classmethod def document(cls, baseurl=None): """ Factory method to create an `presalytics.lib.widgets.ooxml.OoxmlEndpointMap` instance targeting Document objects """ return cls(OoxmlEndpointMap._DOCUMENT, baseurl) @classmethod def group(cls, baseurl=None): """ Factory method to create an `presalytics.lib.widgets.ooxml.OoxmlEndpointMap` instance targeting Group objects """ return cls(OoxmlEndpointMap._GROUP, baseurl) @classmethod def image(cls, baseurl=None): """ Factory method to create an `presalytics.lib.widgets.ooxml.OoxmlEndpointMap` instance targeting Image objects """ return cls(OoxmlEndpointMap._IMAGE, baseurl) @classmethod def shape(cls, baseurl=None): """ Factory method to create an `presalytics.lib.widgets.ooxml.OoxmlEndpointMap` instance targeting Shape objects """ return cls(OoxmlEndpointMap._SHAPE, baseurl) @classmethod def shapetree(cls, baseurl=None): """ Factory method to create an `presalytics.lib.widgets.ooxml.OoxmlEndpointMap` instance targeting ShapeTree objects """ return cls(OoxmlEndpointMap._SHAPETREE, baseurl) @classmethod def slide(cls, baseurl=None): """ Factory method to create an `presalytics.lib.widgets.ooxml.OoxmlEndpointMap` instance targeting Slide objects """ return cls(OoxmlEndpointMap._SLIDE, baseurl) @classmethod def table(cls, baseurl=None): """ Factory method to create an `presalytics.lib.widgets.ooxml.OoxmlEndpointMap` instance targeting Table objects """ return cls(OoxmlEndpointMap._TABLE, baseurl) @classmethod def theme(cls, baseurl=None): """ Factory method to create an `presalytics.lib.widgets.ooxml.OoxmlEndpointMap` instance targeting Theme objects """ return cls(OoxmlEndpointMap._THEME, baseurl)
Static methods
def connection_shape(baseurl=None)
-
Factory method to create an
OoxmlEndpointMap
instance targeting ConnectionShape objectsExpand source code Browse git
@classmethod def connection_shape(cls, baseurl=None): """ Factory method to create an `presalytics.lib.widgets.ooxml.OoxmlEndpointMap` instance targeting ConnectionShape objects """ return cls(OoxmlEndpointMap._CONNECTION_SHAPE, baseurl)
def chart(baseurl=None)
-
Factory method to create an
OoxmlEndpointMap
instance targeting Chart objectsExpand source code Browse git
@classmethod def chart(cls, baseurl=None): """ Factory method to create an `presalytics.lib.widgets.ooxml.OoxmlEndpointMap` instance targeting Chart objects """ return cls(OoxmlEndpointMap._CHART, baseurl)
def document(baseurl=None)
-
Factory method to create an
OoxmlEndpointMap
instance targeting Document objectsExpand source code Browse git
@classmethod def document(cls, baseurl=None): """ Factory method to create an `presalytics.lib.widgets.ooxml.OoxmlEndpointMap` instance targeting Document objects """ return cls(OoxmlEndpointMap._DOCUMENT, baseurl)
def group(baseurl=None)
-
Factory method to create an
OoxmlEndpointMap
instance targeting Group objectsExpand source code Browse git
@classmethod def group(cls, baseurl=None): """ Factory method to create an `presalytics.lib.widgets.ooxml.OoxmlEndpointMap` instance targeting Group objects """ return cls(OoxmlEndpointMap._GROUP, baseurl)
def image(baseurl=None)
-
Factory method to create an
OoxmlEndpointMap
instance targeting Image objectsExpand source code Browse git
@classmethod def image(cls, baseurl=None): """ Factory method to create an `presalytics.lib.widgets.ooxml.OoxmlEndpointMap` instance targeting Image objects """ return cls(OoxmlEndpointMap._IMAGE, baseurl)
def shape(baseurl=None)
-
Factory method to create an
OoxmlEndpointMap
instance targeting Shape objectsExpand source code Browse git
@classmethod def shape(cls, baseurl=None): """ Factory method to create an `presalytics.lib.widgets.ooxml.OoxmlEndpointMap` instance targeting Shape objects """ return cls(OoxmlEndpointMap._SHAPE, baseurl)
def shapetree(baseurl=None)
-
Factory method to create an
OoxmlEndpointMap
instance targeting ShapeTree objectsExpand source code Browse git
@classmethod def shapetree(cls, baseurl=None): """ Factory method to create an `presalytics.lib.widgets.ooxml.OoxmlEndpointMap` instance targeting ShapeTree objects """ return cls(OoxmlEndpointMap._SHAPETREE, baseurl)
def slide(baseurl=None)
-
Factory method to create an
OoxmlEndpointMap
instance targeting Slide objectsExpand source code Browse git
@classmethod def slide(cls, baseurl=None): """ Factory method to create an `presalytics.lib.widgets.ooxml.OoxmlEndpointMap` instance targeting Slide objects """ return cls(OoxmlEndpointMap._SLIDE, baseurl)
def table(baseurl=None)
-
Factory method to create an
OoxmlEndpointMap
instance targeting Table objectsExpand source code Browse git
@classmethod def table(cls, baseurl=None): """ Factory method to create an `presalytics.lib.widgets.ooxml.OoxmlEndpointMap` instance targeting Table objects """ return cls(OoxmlEndpointMap._TABLE, baseurl)
def theme(baseurl=None)
-
Factory method to create an
OoxmlEndpointMap
instance targeting Theme objectsExpand source code Browse git
@classmethod def theme(cls, baseurl=None): """ Factory method to create an `presalytics.lib.widgets.ooxml.OoxmlEndpointMap` instance targeting Theme objects """ return cls(OoxmlEndpointMap._THEME, baseurl)
Methods
def get_object_type(self)
-
Returns the Ooxml Automation service object type for this endpoint
Expand source code Browse git
def get_object_type(self): """ Returns the Ooxml Automation service object type for this endpoint """ for key, val in self.OBJECT_TYPE_MAP.items(): for test_ep in val: if test_ep == self.endpoint_id: if key == "EMPTY": return self.endpoint_id else: return "{0}.{1}".format(key, self.endpoint_id) message = "Invalid EndpointMap configuration: {0} is not in OBJECT_TYPE_MAP".format(self.endpoint_id) raise presalytics.lib.exceptions.ValidationError(message)
def get_id_url(self, id)
-
Returns a url to pull metadata from this Ooxml Automation service endpoint
Expand source code Browse git
def get_id_url(self, id): """ Returns a url to pull metadata from this Ooxml Automation service endpoint """ return posixpath.join(self.root_url, id)
def get_svg_url(self, id)
-
Returns a url to download an svg from this Ooxml Automation service endpoint
Expand source code Browse git
def get_svg_url(self, id): """ Returns a url to download an svg from this Ooxml Automation service endpoint """ return posixpath.join(self.root_url, "Svg", id)
def get_xml_url(self, id)
-
Returns a url to pull the Open Office Xml from this Ooxml Automation service endpoint
Expand source code Browse git
def get_xml_url(self, id): """ Returns a url to pull the Open Office Xml from this Ooxml Automation service endpoint """ return posixpath.join(self.root_url, "OpenOfficeXml", id)
class OoxmlWidgetBase (name, story_id=None, object_ooxml_id=None, endpoint_map=None, **kwargs)
-
Base class for creating widgets from objects at endpoints in the Presalytics API Ooxml Automation service.
Parameters
name
:str
, optional- The widget name.
If not provided, will be the
object_name
orfilename
story_id
:str
, optional- The the id of the story in the Presalytics API Story service. If not provided, a new story will be created. Do not supply if this object has not yet been created.
object_ooxml_id
:str
, optional- The identifier of the Ooxml Automation service object bound the Story. Do not supply if this object has not yet been created.
endpoint_map
:OoxmlEndpointMap
, optional- Reference to the Presalytics API Ooxml Automation service endpoint and object type that for the object of interest
Attributes
svg_html
:str
- An html fragment containing the svg data
Expand source code Browse git
class OoxmlWidgetBase(presalytics.story.components.WidgetBase): """ Base class for creating widgets from objects at endpoints in the Presalytics API Ooxml Automation service. Parameters ---------- name : str, optional The widget name. If not provided, will be the `object_name` or `filename` story_id : str, optional The the id of the story in the Presalytics API Story service. If not provided, a new story will be created. Do not supply if this object has not yet been created. object_ooxml_id : str, optional The identifier of the Ooxml Automation service object bound the Story. Do not supply if this object has not yet been created. endpoint_map : presalytics.lib.widgets.ooxml.OoxmlEndpointMap, optional Reference to the Presalytics API Ooxml Automation service endpoint and object type that for the object of interest Attributes ---------- svg_html : str An html fragment containing the svg data """ endpoint_map: OoxmlEndpointMap data: typing.Dict __component_kind__ = "ooxml-base-widget" __plugins__ = [ { 'name': 'external_scripts', 'kind': 'script', 'config': { 'approved_scripts_key': 'jquery' } }, { 'name': 'external_scripts', 'kind': 'script', 'config': { 'approved_scripts_key': 'ooxml' } }, { 'name': 'external_links', 'kind': 'style', 'config': { 'approved_styles_key': 'preloaders' } } ] def __init__(self, name, story_id=None, object_ooxml_id=None, endpoint_map=None, **kwargs): super(OoxmlWidgetBase, self).__init__(name, **kwargs) if object_ooxml_id: self.object_ooxml_id = object_ooxml_id if story_id: self.story_id = story_id if endpoint_map: self.endpoint_map = endpoint_map self.svg_html = None def create_container(self, **kwargs): """ Wraps the Presalytics API Ooxml Automation service SVG endpoint in an `<iframe>` that will be rendered inside of a story """ svg_container_div = lxml.html.Element("div", { 'class': 'svg-container', 'data-object-type': self.endpoint_map.endpoint_id, 'data-object-id': self.object_ooxml_id }) preloader_container_div = lxml.html.Element( "div", {"class":"preloader-container"}) preloader_row_div = lxml.etree.SubElement(preloader_container_div, "div", attrib={"class":"preloader-row"}) preloader_file = os.path.join(os.path.dirname(__file__), "img", "preloader.svg") svg = lxml.html.parse(preloader_file) preloader_row_div.append(svg.getroot()) empty_parent_div = lxml.html.Element("div", { 'class': 'empty-parent bg-light' }) empty_parent_div.extend([svg_container_div, preloader_container_div]) return lxml.html.tostring(empty_parent_div) def to_html(self, **kwargs): """ Returns an html string that will render the object at the endpoint """ html = self.create_container() try: html = html.decode('utf-8') except Exception: pass self.svg_html = html return self.svg_html def get_svg(self, id, timeout_iterator=0) -> str: """ Get an svg-formatted version of the object from the Ooxml Automation service """ svg_url = self.endpoint_map.get_svg_url(id) client = self.get_client() headers = client.get_auth_header() headers.update(client.get_request_id_header()) response = requests.get(svg_url, headers=headers) svg_data = response.text if response.status_code != 200: raise presalytics.lib.exceptions.ApiError(message=response.text, status_code=response.status_code) if response.text.startswith("Temp data"): if timeout_iterator > 5: raise presalytics.lib.exceptions.ApiError(message="Unable to download svg. Please check for upstream processing errors.") else: time.sleep(2) svg_data = self.get_svg(id, timeout_iterator) return svg_data def get_svg_file(self, filename=None): """ Writes an svg representation of the object to current working directory. Filname is optional. """ if not filename: filename = self.endpoint_map.get_object_type() + "-" + self.object_ooxml_id + ".pptx" with open(filename, 'w') as f: f.write(self.get_svg(self.object_ooxml_id)) def serialize(self): data = { "story_id": self.story_id, "object_id": self.object_ooxml_id, "endpoint_id": self.endpoint_map.endpoint_id } widget = presalytics.story.outline.Widget( name=self.name, kind=self.__component_kind__, data=data, plugins=None ) return widget @classmethod def deserialize(cls, component, **kwargs): return cls( component.name, component.data["story_id"], component.data["object_id"], OoxmlEndpointMap(component.data["endpoint_id"]), **kwargs )
Ancestors
- WidgetBase
- ComponentBase
- abc.ABC
Subclasses
Methods
def create_container(self, **kwargs)
-
Wraps the Presalytics API Ooxml Automation service SVG endpoint in an
<iframe>
that will be rendered inside of a storyExpand source code Browse git
def create_container(self, **kwargs): """ Wraps the Presalytics API Ooxml Automation service SVG endpoint in an `<iframe>` that will be rendered inside of a story """ svg_container_div = lxml.html.Element("div", { 'class': 'svg-container', 'data-object-type': self.endpoint_map.endpoint_id, 'data-object-id': self.object_ooxml_id }) preloader_container_div = lxml.html.Element( "div", {"class":"preloader-container"}) preloader_row_div = lxml.etree.SubElement(preloader_container_div, "div", attrib={"class":"preloader-row"}) preloader_file = os.path.join(os.path.dirname(__file__), "img", "preloader.svg") svg = lxml.html.parse(preloader_file) preloader_row_div.append(svg.getroot()) empty_parent_div = lxml.html.Element("div", { 'class': 'empty-parent bg-light' }) empty_parent_div.extend([svg_container_div, preloader_container_div]) return lxml.html.tostring(empty_parent_div)
def to_html(self, **kwargs)
-
Returns an html string that will render the object at the endpoint
Expand source code Browse git
def to_html(self, **kwargs): """ Returns an html string that will render the object at the endpoint """ html = self.create_container() try: html = html.decode('utf-8') except Exception: pass self.svg_html = html return self.svg_html
def get_svg(self, id, timeout_iterator=0)
-
Get an svg-formatted version of the object from the Ooxml Automation service
Expand source code Browse git
def get_svg(self, id, timeout_iterator=0) -> str: """ Get an svg-formatted version of the object from the Ooxml Automation service """ svg_url = self.endpoint_map.get_svg_url(id) client = self.get_client() headers = client.get_auth_header() headers.update(client.get_request_id_header()) response = requests.get(svg_url, headers=headers) svg_data = response.text if response.status_code != 200: raise presalytics.lib.exceptions.ApiError(message=response.text, status_code=response.status_code) if response.text.startswith("Temp data"): if timeout_iterator > 5: raise presalytics.lib.exceptions.ApiError(message="Unable to download svg. Please check for upstream processing errors.") else: time.sleep(2) svg_data = self.get_svg(id, timeout_iterator) return svg_data
def get_svg_file(self, filename=None)
-
Writes an svg representation of the object to current working directory. Filname is optional.
Expand source code Browse git
def get_svg_file(self, filename=None): """ Writes an svg representation of the object to current working directory. Filname is optional. """ if not filename: filename = self.endpoint_map.get_object_type() + "-" + self.object_ooxml_id + ".pptx" with open(filename, 'w') as f: f.write(self.get_svg(self.object_ooxml_id))
Inherited members
class OoxmlEditorWidget (name, story_id, object_ooxml_id, endpoint_map, transform_class, transform_params={}, **kwargs)
-
Edits a
widget
from a Presentation or Spreadsheet document and renders the edited widget.This class interacts with the Presalytics API to extract SVG objects from Presentation and spreadsheet documents, from Presaltytics Ooxml Automation service objects that have already been loaded into the API. This class requires that users supply
transform_function
by subclassingXmlTransformBase
, and an optional set of parameters to act as variables in the transform function.Parameters
filename
:str
- The local filepath a presentation or spreadsheet file containing the object to be rendered
name
:str
- The widget name.
If not provided, attribute will be set as the
object_name
orfilename
story_id
:str
- The the id of the story in the Presalytics API Story service. If not provided, a new story will be created. Do not supply if this object has not yet been created.
object_ooxml_id
:str
- The identifier of the Ooxml Automation service object bound the Story. Do not supply if this object has not yet been created.
endpoint_map
:OoxmlEndpointMap
- Reference to the Presalytics API Ooxml Automation service endpoint and object type that for the object of interest
tranform_class
:subclass
ofXmlTransformBase
- A class containing a
transform_function
method that transforms Open Office Xml via anlxml.etree.Element
instance transform_params
:dict
, optional- A dictionary of parameters that will be passed to the
transform_class
'stransform_function
as variables to modify the underlying OpenOfficeXml
Expand source code Browse git
class OoxmlEditorWidget(presalytics.lib.widgets.ooxml.OoxmlWidgetBase): """ Edits a `widget` from a Presentation or Spreadsheet document and renders the edited widget. This class interacts with the Presalytics API to extract SVG objects from Presentation and spreadsheet documents, from Presaltytics Ooxml Automation service objects that have already been loaded into the API. This class requires that users supply `transform_function` by subclassing `presalytics.lib.widgets.ooxml_editors.XmlTransformBase`, and an optional set of parameters to act as variables in the transform function. Parameters ---------- filename : str The local filepath a presentation or spreadsheet file containing the object to be rendered name : str The widget name. If not provided, attribute will be set as the `object_name` or `filename` story_id : str The the id of the story in the Presalytics API Story service. If not provided, a new story will be created. Do not supply if this object has not yet been created. object_ooxml_id : str The identifier of the Ooxml Automation service object bound the Story. Do not supply if this object has not yet been created. endpoint_map : presalytics.lib.widgets.ooxml.OoxmlEndpointMap Reference to the Presalytics API Ooxml Automation service endpoint and object type that for the object of interest tranform_class : subclass of presalytics.lib.widgets.ooxml_editors.XmlTransformBase A class containing a `transform_function` method that transforms Open Office Xml via an `lxml.etree.Element` instance transform_params : dict, optional A dictionary of parameters that will be passed to the `transform_class`'s `transform_function` as variables to modify the underlying OpenOfficeXml """ transform: XmlTransformBase __component_kind__ = 'ooxml-xml-editor' def __init__(self, name: str, story_id, object_ooxml_id, endpoint_map, transform_class, transform_params={}, **kwargs): super(OoxmlEditorWidget, self).__init__(name, story_id=story_id, object_ooxml_id=object_ooxml_id, endpoint_map=endpoint_map, **kwargs) self.name = name self.transform = transform_class(transform_params) self.update() self.svg_html = self.create_container(**self.client_info) self.outline_widget = self.serialize() def update_xml(self, xml_str) -> str: """ Runs the `transform_function` Open Office Xml data downloaded via the Presalytics API """ xml = lxml.etree.fromstring(xml_str) new_xml = self.transform.execute(xml) new_xml_str = lxml.etree.tostring(new_xml) return new_xml_str def update(self): """ Update the widget, include changes to the Xml """ client = self.get_client() headers = client.get_auth_header() headers.update(client.get_request_id_header()) if self.transform: xml_url = self.endpoint_map.get_xml_url(self.object_ooxml_id) xml_response = requests.get(xml_url + "?updated=false", headers=headers) if xml_response.status_code != 200: raise presalytics.lib.exceptions.ApiError(message=xml_response.text) dto = xml_response.json() new_xml = self.update_xml(dto["openOfficeXml"]) dto["openOfficeXml"] = new_xml.decode('utf-8') xml_update_response = requests.put(xml_url, json=dto, headers=headers) if xml_response.status_code != 200: raise presalytics.lib.exceptions.ApiError(message=xml_update_response.content) @classmethod def deserialize(cls, component, **kwargs): endpoint_map = presalytics.lib.widgets.ooxml.OoxmlEndpointMap(component.data["endpoint_id"]) class_key = "XmlTransform." + component.data.get("transform_class", "") transform_class = get_transform_registry().get(class_key) transform_params = component.data.get("transform_params", {}) return cls(component.name, component.data["story_id"], component.data["object_ooxml_id"], endpoint_map, transform_class=transform_class, transform_params=transform_params, **kwargs) def serialize(self, **kwargs): data = { "story_id": self.story_id, "object_ooxml_id": self.object_ooxml_id, "endpoint_id": self.endpoint_map.endpoint_id, "transform_class": self.transform.__xml_transform_name__, "transform_params": self.transform.function_params } widget = presalytics.story.outline.Widget( name=self.name, data=data, kind=self.__component_kind__ ) return widget
Ancestors
- OoxmlWidgetBase
- WidgetBase
- ComponentBase
- abc.ABC
Methods
def update_xml(self, xml_str)
-
Runs the
transform_function
Open Office Xml data downloaded via the Presalytics APIExpand source code Browse git
def update_xml(self, xml_str) -> str: """ Runs the `transform_function` Open Office Xml data downloaded via the Presalytics API """ xml = lxml.etree.fromstring(xml_str) new_xml = self.transform.execute(xml) new_xml_str = lxml.etree.tostring(new_xml) return new_xml_str
def update(self)
-
Update the widget, include changes to the Xml
Expand source code Browse git
def update(self): """ Update the widget, include changes to the Xml """ client = self.get_client() headers = client.get_auth_header() headers.update(client.get_request_id_header()) if self.transform: xml_url = self.endpoint_map.get_xml_url(self.object_ooxml_id) xml_response = requests.get(xml_url + "?updated=false", headers=headers) if xml_response.status_code != 200: raise presalytics.lib.exceptions.ApiError(message=xml_response.text) dto = xml_response.json() new_xml = self.update_xml(dto["openOfficeXml"]) dto["openOfficeXml"] = new_xml.decode('utf-8') xml_update_response = requests.put(xml_url, json=dto, headers=headers) if xml_response.status_code != 200: raise presalytics.lib.exceptions.ApiError(message=xml_update_response.content)
Inherited members
class D3Widget (name, d3_data, id=None, story_id=None, script64=None, script_filename=None, css64=None, css_filename=None, html64=None, html_filename=None, *args, **kwargs)
-
A
Widget
for rendering user-defined d3.js scriptsThis class allows users to load d3.js objects into widgets in order to create custom and interactive widgets. User can define d3 scripts in a separate file and load them via
script_filename
parameter.Parameters
name
:str
- the name of widget.
Must be unique within
COMPONENTS
d3_data
:dict
- Data that will be loaded into the script when run in the browser. Avaiable
in the script as the
data
object. id
:str
, optional- A unique identifier the widget. Automatically generated when the widget is created or updated.
story_id
:str
, optional- The story_id of the parent story
script64
:str.
optional- A base64-encoded string of the script's text. Used for server-to-server transport over https
script_filename
:str
, optional- Required when updating the script text.
Read into the
script64
parameter via theread_file
method html64
:str.
optional- A base64-encoded string of the html fragments's text. Used for server-to-server transport over https
html_filename
:str
, optional- The name of file in the local directory with an html framement that should be rendered within the
body (inside element
<div id="{{id}}" class="d3-container"></div>
) of the iframe containing the d3 script css64
:str.
optional- A base64-encoded string of the css styles to apply to the d3 document. Used for server-to-server transport over https.
css_filename
:str
, optional-
A css file containing styles that will be applied to d3
Note: Styles
html {width: 100%; height:100%;} body {width: 100%; height: 100%; margin: 0px;}
are applied by default if not css is provided
Script Local Variables:
The following vairables are avialable to users when writing scripts:
data:
javascript object
The data loaded into the script viad3_data
parametercontainer:
html element
The first div in the bodyd3:
javascript object
The root d3 object for selecting, creating and editing elements on the DOMSecurity Note:
Scripts loaded via this widget are Sandboxed. These script can only interact with dom elements defined in the widget script loaded via the
script_filename
parameter. Fetch and xhr actions are also disabled via a restrictive Content Security Policy.Expand source code Browse git
class D3Widget(presalytics.story.components.WidgetBase): """ A `Widget` for rendering user-defined d3.js scripts This class allows users to load [d3.js](https://d3js.org/) objects into widgets in order to create custom and interactive widgets. User can define d3 scripts in a separate file and load them via `script_filename` parameter. Parameters ---------- name : str the name of widget. Must be unique within `presalytics.COMPONENTS` d3_data: dict Data that will be loaded into the script when run in the browser. Avaiable in the script as the `data` object. id: str, optional A unique identifier the widget. Automatically generated when the widget is created or updated. story_id : str, optional The story_id of the parent story script64 : str. optional A base64-encoded string of the script's text. Used for server-to-server transport over https script_filename: str, optional Required when updating the script text. Read into the `script64` parameter via the `read_file` method html64 : str. optional A base64-encoded string of the html fragments's text. Used for server-to-server transport over https html_filename: str, optional The name of file in the local directory with an html framement that should be rendered within the body (inside element `<div id="{{id}}" class="d3-container"></div>`) of the iframe containing the d3 script css64 : str. optional A base64-encoded string of the css styles to apply to the d3 document. Used for server-to-server transport over https. css_filename: str, optional A css file containing styles that will be applied to d3 Note: Styles `html {width: 100%; height:100%;} body {width: 100%; height: 100%; margin: 0px;}` are applied by default if not css is provided Script Local Variables: ---------- The following vairables are avialable to users when writing scripts: data: `javascript object` The data loaded into the script via `d3_data` parameter container: `html element` The first div in the body d3: `javascript object` The root d3 object for selecting, creating and editing elements on the DOM Security Note: ---------- Scripts loaded via this widget are *Sandboxed*. These script can only interact with dom elements defined in the widget script loaded via the `script_filename` parameter. Fetch and xhr actions are also disabled via a restrictive Content Security Policy. """ __component_kind__ = 'd3' __plugins__ = [ { 'name': 'external_scripts', 'kind': 'script', 'config': { 'approved_scripts_key': 'd3' } } ] def __init__(self, name: str, d3_data: typing.Dict, id: str = None, story_id: str = None, script64: str = None, script_filename: str = None, css64: str = None, css_filename: str = None, html64: str = None, html_filename: str = None, *args, **kwargs): if not id: id = 'd3-' + str(uuid.uuid4()) self.id = id self.d3_data = d3_data self.story_id = story_id super(D3Widget, self).__init__(name, *args, **kwargs) self.script_filename = script_filename self.script64 = self.read_file(script_filename) if not self.script64: self.script64 = script64 if not self.script64: raise presalytics.lib.exceptions.InvalidConfigurationError("D3 Widget must be supplied either a script64 or script_filename keyword argument.") self.html64 = self.read_file(html_filename) self.html64 = self.html64 if self.html64 else html64 self.css64 = self.read_file(css_filename) self.css64 = self.css64 if self.css64 else css64 self.html_filename = html_filename self.css_filename = css_filename def read_file(self, filename) -> typing.Optional[str]: """ Find a file named `filename` and return its base64-ecoded content """ data64 = None if filename: search_paths = list(set(presalytics.autodiscover_paths)) if os.getcwd() not in search_paths: search_paths.append(os.getcwd()) for path in search_paths: fpath = os.path.join(path, filename) if os.path.exists(fpath): with open(fpath, 'rb') as f: data = f.read() data64 = base64.b64encode(data).decode('utf-8') #type: ignore break if not data64: logger.debug("File {0} could not be found".format(filename)) return data64 def to_html(self, data=None, **kwargs) -> str: """ Renders the sandboxed iframe with will contain the d3 script widget """ if not self.story_id: message = "This object requires a valid story_id to render." raise presalytics.lib.exceptions.MissingConfigException(message=message) html = self.create_container() return html def create_container(self, **kwargs): """ Wraps the D3 objects in an endpoint at the story API load via a sandboxed `<iframe>` that will be rendered """ params = { "story_host": self.get_client(delegate_login=True).story.api_client.external_root_url, "id": self.id, "story_id": self.story_id, } source_url = "{story_host}/{story_id}/d3/{id}".format(**params) empty_parent_div = lxml.html.Element("div", { 'class': 'empty-parent bg-light', 'style': 'height: 100%; width: 100%, display: block; text-align: left;' }) frame = lxml.html.Element("iframe", { 'src': source_url, 'frameborder': "0", 'scrolling': "auto", 'class': 'd3-responsive-frame', 'style': 'max-height: none; max-width: none; height:100%; width: 100%;', 'sandbox': 'allow-forms allow-scripts' }) empty_parent_div.append(frame) return lxml.html.tostring(empty_parent_div).decode('utf-8') @classmethod def deserialize(cls, outline, **kwargs): d3_data = outline.data.get("d3_data") story_id = outline.data.get("story_id", None) id = outline.data.get('id', None) data = outline.data.get('d3_data', None) script_filename = outline.data.get('script_filename', None) script64 = outline.data.get('script64', None) html_filename = outline.data.get('html_filename', None) html64 = outline.data.get('html64', None) css_filename = outline.data.get('css_filename', None) css64 = outline.data.get('css64', None) return cls(outline.name, d3_data, id=id, story_id=story_id, script64=script64, script_filename=script_filename, html64=html64, html_filename=html_filename, css64=css64, css_filename=css_filename, **kwargs) def serialize(self, **kwargs): data = { 'd3_data': self.d3_data, 'id': self.id, 'story_id': self.story_id, 'script_filename': self.script_filename, 'script64': self.script64, 'css64': self.css64, 'html64': self.html64, 'html_filename': self.html_filename, 'css_filename': self.css_filename } return presalytics.story.outline.Widget( name=self.name, kind=self.__component_kind__, data=data, ) DEFAULT_CSS = """ html { height: 100%; width: 100%; } body { height: 100%; width: 100%; margin: 0px; } """ def standalone_html(self) -> str: """ Returns string with an html document containing that d3 widget Loaded via the Story API d3 endpoint """ SIMPLE_HTML = jinja2.Template("""<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <style> {{ css }} </style> </head> <body> <script type="text/javascript" src="{{ d3_url }}"></script> <div id="{{ id }}" class="d3-container">{{ html_fragment }}</div> <script type="text/javascript"> var id = '{{id}}'; var data = JSON.parse('{{data|safe}}'); var container = document.getElementById(id); {{script|safe}} </script> </body> </html>""") data = json.dumps(self.d3_data) # dont use hyphens in data keys script = base64.b64decode(self.script64).decode('utf-8') #type: ignore #Required extra_css = base64.b64decode(self.css64).decode('utf-8') if self.css64 else D3Widget.DEFAULT_CSS #type: ignore html_fragment = base64.b64decode(self.html64).decode('utf-8') if self.html64 else None #type: ignore # disable nested iframes context = { "id": self.id, "d3_url": presalytics.lib.plugins.external.ApprovedExternalScripts().attr_dict.flatten().get('d3'), "data": data, "script": script, "css": extra_css, "html_fragment": html_fragment } return SIMPLE_HTML.render(**context)
Ancestors
- WidgetBase
- ComponentBase
- abc.ABC
Class variables
var DEFAULT_CSS
-
str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str
Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.str() (if defined) or repr(object). encoding defaults to sys.getdefaultencoding(). errors defaults to 'strict'.
Methods
def read_file(self, filename)
-
Find a file named
filename
and return its base64-ecoded contentExpand source code Browse git
def read_file(self, filename) -> typing.Optional[str]: """ Find a file named `filename` and return its base64-ecoded content """ data64 = None if filename: search_paths = list(set(presalytics.autodiscover_paths)) if os.getcwd() not in search_paths: search_paths.append(os.getcwd()) for path in search_paths: fpath = os.path.join(path, filename) if os.path.exists(fpath): with open(fpath, 'rb') as f: data = f.read() data64 = base64.b64encode(data).decode('utf-8') #type: ignore break if not data64: logger.debug("File {0} could not be found".format(filename)) return data64
def to_html(self, data=None, **kwargs)
-
Renders the sandboxed iframe with will contain the d3 script widget
Expand source code Browse git
def to_html(self, data=None, **kwargs) -> str: """ Renders the sandboxed iframe with will contain the d3 script widget """ if not self.story_id: message = "This object requires a valid story_id to render." raise presalytics.lib.exceptions.MissingConfigException(message=message) html = self.create_container() return html
def create_container(self, **kwargs)
-
Wraps the D3 objects in an endpoint at the story API load via a sandboxed
<iframe>
that will be renderedExpand source code Browse git
def create_container(self, **kwargs): """ Wraps the D3 objects in an endpoint at the story API load via a sandboxed `<iframe>` that will be rendered """ params = { "story_host": self.get_client(delegate_login=True).story.api_client.external_root_url, "id": self.id, "story_id": self.story_id, } source_url = "{story_host}/{story_id}/d3/{id}".format(**params) empty_parent_div = lxml.html.Element("div", { 'class': 'empty-parent bg-light', 'style': 'height: 100%; width: 100%, display: block; text-align: left;' }) frame = lxml.html.Element("iframe", { 'src': source_url, 'frameborder': "0", 'scrolling': "auto", 'class': 'd3-responsive-frame', 'style': 'max-height: none; max-width: none; height:100%; width: 100%;', 'sandbox': 'allow-forms allow-scripts' }) empty_parent_div.append(frame) return lxml.html.tostring(empty_parent_div).decode('utf-8')
def standalone_html(self)
-
Returns string with an html document containing that d3 widget
Loaded via the Story API d3 endpoint
Expand source code Browse git
def standalone_html(self) -> str: """ Returns string with an html document containing that d3 widget Loaded via the Story API d3 endpoint """ SIMPLE_HTML = jinja2.Template("""<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <style> {{ css }} </style> </head> <body> <script type="text/javascript" src="{{ d3_url }}"></script> <div id="{{ id }}" class="d3-container">{{ html_fragment }}</div> <script type="text/javascript"> var id = '{{id}}'; var data = JSON.parse('{{data|safe}}'); var container = document.getElementById(id); {{script|safe}} </script> </body> </html>""") data = json.dumps(self.d3_data) # dont use hyphens in data keys script = base64.b64decode(self.script64).decode('utf-8') #type: ignore #Required extra_css = base64.b64decode(self.css64).decode('utf-8') if self.css64 else D3Widget.DEFAULT_CSS #type: ignore html_fragment = base64.b64decode(self.html64).decode('utf-8') if self.html64 else None #type: ignore # disable nested iframes context = { "id": self.id, "d3_url": presalytics.lib.plugins.external.ApprovedExternalScripts().attr_dict.flatten().get('d3'), "data": data, "script": script, "css": extra_css, "html_fragment": html_fragment } return SIMPLE_HTML.render(**context)
Inherited members
class ChartUpdaterWidget (name, story_id, chart_id, dto=None, data_table=None, **kwargs)
-
Updates a Chart in the Ooxml Automation service API at the the endpoint '/Chart/ChartUpdate/'
This class simplifies chart updates, for charts residing in the Ooxml Automation Service, allowing updates to ooxml object data and its underlying xml either via a list of lists or the
ChartChartDataDTO
object.Parameters
name
:str
- A name for the widget.
story_id
:str
- The the id of the story in the Presalytics API Story service.
chart_id
:str
- The identifier of the Ooxml Automation Chart service object.
dto
:ChartChartDataDTO
, optional- A an instance of the data transfer object model. The class of this object is defined by the
_get_dto_class()
method. Represents the current state of the data of the data in the service. data_table
:list
oflists
, optional- A representation of a data table that will be incorporates into a dto object's property defined by
the subclass'
_get_dto_table_name
method.
Expand source code Browse git
class ChartUpdaterWidget(UpdaterWidgetBase): """ Updates a Chart in the Ooxml Automation service API at the the endpoint '/Chart/ChartUpdate/' This class simplifies chart updates, for charts residing in the Ooxml Automation Service, allowing updates to ooxml object data and its underlying xml either via a list of lists or the `presalytics.client.presalytics_ooxml_automation.models.chart_chart_data_dto.ChartChartDataDTO` object. Parameters ---------- name : str A name for the widget. story_id : str The the id of the story in the Presalytics API Story service. chart_id : str The identifier of the Ooxml Automation Chart service object. dto: presalytics.client.presalytics_ooxml_automation.models.chart_chart_data_dto.ChartChartDataDTO, optional A an instance of the data transfer object model. The class of this object is defined by the `_get_dto_class()` method. Represents the current state of the data of the data in the service. data_table: list of lists, optional A representation of a data table that will be incorporates into a dto object's property defined by the subclass' `_get_dto_table_name` method. """ __component_kind__ = "chart-updater" def __init__(self, name, story_id: str, chart_id: str, dto: 'ChartChartDataDTO' = None, data_table: typing.Sequence[typing.Sequence] = None, **kwargs): super().__init__(name, story_id, chart_id, OoxmlEndpointMap.chart(), dto=dto, data_table=data_table, **kwargs) self.chart_id = chart_id def _get_dto_class(self): return presalytics.client.presalytics_ooxml_automation.models.chart_chart_data_dto.ChartChartDataDTO def _get_endpoint_path(self): return "ChartUpdate" def _get_dto_table_name(self): return "data_points" def get_dataframe(self) -> pandas.DataFrame: """ Returns a panda datagrame of the """ data: collections.OrderedDict if not self.dto: self.dto = self.get_dto() data = collections.OrderedDict() for i in range(0, len(self.dto.series_names)): data.update({ self.dto.series_names[i]: pandas.Series(self.dto.data_points[i], self.dto.category_names) }) return pandas.DataFrame(data) def put_dataframe(self, df: pandas.DataFrame): data_dict = df.to_dict('split') data_points = list(map(list, zip(*data_dict['data']))) dto = self._get_dto_class()(chart_id=self.chart_id, series_names=data_dict["columns"], category_names=data_dict["index"], data_points=data_points) self._put_dto(dto)
Ancestors
Methods
def get_dataframe(self)
-
Returns a panda datagrame of the
Expand source code Browse git
def get_dataframe(self) -> pandas.DataFrame: """ Returns a panda datagrame of the """ data: collections.OrderedDict if not self.dto: self.dto = self.get_dto() data = collections.OrderedDict() for i in range(0, len(self.dto.series_names)): data.update({ self.dto.series_names[i]: pandas.Series(self.dto.data_points[i], self.dto.category_names) }) return pandas.DataFrame(data)
def put_dataframe(self, df)
-
Expand source code Browse git
def put_dataframe(self, df: pandas.DataFrame): data_dict = df.to_dict('split') data_points = list(map(list, zip(*data_dict['data']))) dto = self._get_dto_class()(chart_id=self.chart_id, series_names=data_dict["columns"], category_names=data_dict["index"], data_points=data_points) self._put_dto(dto)
Inherited members
class TableUpdaterWidget (name, story_id, table_id, dto=None, data_table=None, **kwargs)
-
Updates a Table in the Ooxml Automation service API at the the endpoint '/Table/TableUpdate/'
This class simplifies table updates, for tables residing in the Ooxml Automation Service, allowing updates to ooxml object data and its underlying xml either via a list of lists or the
TableTableDataDTO
object.Parameters
name
:str
- A name for the widget.
story_id
:str
- The the id of the story in the Presalytics API Story service.
table_id
:str
- The identifier of the Ooxml Automation Table service object.
dto
:TableTableDataDTO
, optional- A an instance of the data transfer object model. The class of this object is defined by the
_get_dto_class()
method. Represents the current state of the data of the data in the service. data_table
:list
oflists
, optional- A representation of a data table that will be incorporates into a dto object's property defined by
the subclass'
_get_dto_table_name
method.
Expand source code Browse git
class TableUpdaterWidget(UpdaterWidgetBase): """ Updates a Table in the Ooxml Automation service API at the the endpoint '/Table/TableUpdate/' This class simplifies table updates, for tables residing in the Ooxml Automation Service, allowing updates to ooxml object data and its underlying xml either via a list of lists or the `presalytics.client.presalytics_ooxml_automation.models.table_table_data_dto.TableTableDataDTO` object. Parameters ---------- name : str A name for the widget. story_id : str The the id of the story in the Presalytics API Story service. table_id : str The identifier of the Ooxml Automation Table service object. dto: presalytics.client.presalytics_ooxml_automation.models.table_table_data_dto.TableTableDataDTO, optional A an instance of the data transfer object model. The class of this object is defined by the `_get_dto_class()` method. Represents the current state of the data of the data in the service. data_table: list of lists, optional A representation of a data table that will be incorporates into a dto object's property defined by the subclass' `_get_dto_table_name` method. """ __component_kind__ = "table-updater" def __init__(self, name, story_id: str, table_id: str, dto: 'TableTableDataDTO' = None, data_table: typing.Sequence[typing.Sequence] = None, **kwargs): super().__init__(name, story_id, table_id, OoxmlEndpointMap.table(), dto=dto, data_table=data_table, **kwargs) self.table_id = table_id def _get_dto_class(self): return presalytics.client.presalytics_ooxml_automation.models.table_table_data_dto.TableTableDataDTO def _get_endpoint_path(self): return "TableUpdate" def _get_dto_table_name(self): return "table_data"
Ancestors
Inherited members
class XmlTransformBase (function_params, *args, **kwargs)
-
Base class for writing Open Office Xml tranform functions to be implemented by
OoxmlEditorWidget
*For more information about Open Office Xml Schema that underlies .pptx and .xlsx files, see http://officeopenxml.com/
*For more information on how to lxml to write transforms, consult https://lxml.de/
Parameters
function_params
:dict
- A dictionary containing variables that will be used when the
transform_function
is executed
Expand source code Browse git
class XmlTransformBase(abc.ABC): """ Base class for writing Open Office Xml tranform functions to be implemented by `presalytics.lib.widgets.ooxml_editors.OoxmlEditorWidget` *For more information about Open Office Xml Schema that underlies .pptx and .xlsx files, see http://officeopenxml.com/ *For more information on how to lxml to write transforms, consult https://lxml.de/ Parameters ---------- function_params : dict A dictionary containing variables that will be used when the `transform_function` is executed """ __xml_transform_kind__ = 'XmlTransform' def __init__(self, function_params: typing.Dict, *args, **kwargs): self.function_params = function_params @abc.abstractmethod def transform_function(self, lxml_element: lxml.etree.Element, params: typing.Dict) -> lxml.etree.Element: """ Modifies Open Office Xml via transforming `lxml.etree.Element` using a params `dict` containing variables. Must be overridden in subclasses. """ pass def execute(self, lxml_element: lxml.etree.Element): """ Called by widget classes (e.g., `presalytics.lib.widgets.ooxml_editors.OoxmlEditorWidget`) to perform the updates prescribed in the `transform_function` """ return self.transform_function(lxml_element, self.function_params)
Ancestors
- abc.ABC
Subclasses
Methods
def transform_function(self, lxml_element, params)
-
Modifies Open Office Xml via transforming
lxml.etree.Element
using a paramsdict
containing variables. Must be overridden in subclasses.Expand source code Browse git
@abc.abstractmethod def transform_function(self, lxml_element: lxml.etree.Element, params: typing.Dict) -> lxml.etree.Element: """ Modifies Open Office Xml via transforming `lxml.etree.Element` using a params `dict` containing variables. Must be overridden in subclasses. """ pass
def execute(self, lxml_element)
-
Called by widget classes (e.g.,
OoxmlEditorWidget
) to perform the updates prescribed in thetransform_function
Expand source code Browse git
def execute(self, lxml_element: lxml.etree.Element): """ Called by widget classes (e.g., `presalytics.lib.widgets.ooxml_editors.OoxmlEditorWidget`) to perform the updates prescribed in the `transform_function` """ return self.transform_function(lxml_element, self.function_params)
class ChangeShapeColor (function_params, *args, **kwargs)
-
Changes the color of a set of Open Office Xml Shapes
Function Parameters Dictionary
hex_color
:str
- The six-digit hexadecimal-format color string (e.g., ffa500 for orange). See https://www.color-hex.com/ for an example color calculator
object_name
:str
, optional- The object tree name of the target shape. If not supplied, all descendent shapes will have their color changed.
Expand source code Browse git
class ChangeShapeColor(XmlTransformBase): """ Changes the color of a set of [Open Office Xml Shapes](http://officeopenxml.com/drwShape.php) Function Parameters Dictionary ---------- hex_color : str The six-digit hexadecimal-format color string (e.g., ffa500 for orange). See https://www.color-hex.com/ for an example color calculator object_name : str, optional The object tree name of the target shape. If not supplied, all descendent shapes will have their color changed. """ __xml_transform_name__ = "ChangeShapeColor" @staticmethod def replace_color_on_target_shape(shape_xml: lxml.etree.Element, new_color: str) -> lxml.etree.Element: """ Changes the color of an [Open Office Xml Shape](http://officeopenxml.com/drwShape.php) """ new_fill = lxml.etree.Element("solidFill") lxml.etree.SubElement(new_fill, "srbgClr", {"val": new_color}) fill_tags = [ 'noFill', 'blipFill', 'gradFill', 'pattFill', 'solidFill' ] props = shape_xml.find('.//{*}spPr') for child in props.getchildren(): if any(re.search('{*}' + r, child.tag) for r in fill_tags): current_nsmap = child.nsmap current_index = props.index(child) current_prefix = child.prefix current_ns = current_nsmap.get(current_prefix, None) new_fill = lxml.etree.Element(lxml.etree.QName(current_ns, "solidFill"), nsmap=current_nsmap) lxml.etree.SubElement(new_fill, lxml.etree.QName(current_ns, "srgbClr"), {"val": new_color}, nsmap=current_nsmap) child.getparent().remove(child) props.insert(current_index, new_fill) return shape_xml def transform_function(self, lxml_element, params): """ Changes the color of an [Open Office Xml Shape](http://officeopenxml.com/drwShape.php) Parameters ----------- lxml_element : lxml.etree.Element An element containing as least one an `<sp>` element params : dict See the `Function Parameters Dictionary` for required entries """ if re.match('{.*}sp', lxml_element.tag): shapes = [lxml_element] else: shapes = lxml_element.findall('.//{*}sp') object_name = params.get("object_name", None) color = params.get("hex_color").lstrip("#").upper() if object_name: for shape in shapes: is_target = False nvprops = shape.find('.//{*}nvSpPr') if len(nvprops) > 0: name = nvprops.find('.//*[@name]').get("name") if name == object_name: is_target = True if is_target: shape = ChangeShapeColor.replace_color_on_target_shape(shape, color) else: for shape in shapes: shape = ChangeShapeColor.replace_color_on_target_shape(shape, color) return lxml_element
Ancestors
- XmlTransformBase
- abc.ABC
Static methods
def replace_color_on_target_shape(shape_xml, new_color)
-
Changes the color of an Open Office Xml Shape
Expand source code Browse git
@staticmethod def replace_color_on_target_shape(shape_xml: lxml.etree.Element, new_color: str) -> lxml.etree.Element: """ Changes the color of an [Open Office Xml Shape](http://officeopenxml.com/drwShape.php) """ new_fill = lxml.etree.Element("solidFill") lxml.etree.SubElement(new_fill, "srbgClr", {"val": new_color}) fill_tags = [ 'noFill', 'blipFill', 'gradFill', 'pattFill', 'solidFill' ] props = shape_xml.find('.//{*}spPr') for child in props.getchildren(): if any(re.search('{*}' + r, child.tag) for r in fill_tags): current_nsmap = child.nsmap current_index = props.index(child) current_prefix = child.prefix current_ns = current_nsmap.get(current_prefix, None) new_fill = lxml.etree.Element(lxml.etree.QName(current_ns, "solidFill"), nsmap=current_nsmap) lxml.etree.SubElement(new_fill, lxml.etree.QName(current_ns, "srgbClr"), {"val": new_color}, nsmap=current_nsmap) child.getparent().remove(child) props.insert(current_index, new_fill) return shape_xml
Methods
def transform_function(self, lxml_element, params)
-
Changes the color of an Open Office Xml Shape
Parameters
lxml_element
:lxml.etree.Element
- An element containing as least one an
<sp>
element params
:dict
- See the
Function Parameters Dictionary
for required entries
Expand source code Browse git
def transform_function(self, lxml_element, params): """ Changes the color of an [Open Office Xml Shape](http://officeopenxml.com/drwShape.php) Parameters ----------- lxml_element : lxml.etree.Element An element containing as least one an `<sp>` element params : dict See the `Function Parameters Dictionary` for required entries """ if re.match('{.*}sp', lxml_element.tag): shapes = [lxml_element] else: shapes = lxml_element.findall('.//{*}sp') object_name = params.get("object_name", None) color = params.get("hex_color").lstrip("#").upper() if object_name: for shape in shapes: is_target = False nvprops = shape.find('.//{*}nvSpPr') if len(nvprops) > 0: name = nvprops.find('.//*[@name]').get("name") if name == object_name: is_target = True if is_target: shape = ChangeShapeColor.replace_color_on_target_shape(shape, color) else: for shape in shapes: shape = ChangeShapeColor.replace_color_on_target_shape(shape, color) return lxml_element
Inherited members
class TextReplace (function_params, *args, **kwargs)
-
Replaces text in a template built into an Office Office Xml object that has been uploaded to the Presalytics Ooxml Automation service. Text to be should be in the format of a "template tag", which is a string enclosed in handlebars as such: '{{template_tag}}'
Function Parameters Dictionary
A dictionary that maps template tags to the new strings that will replace the tags in the rendered widget. The the dictionary keys should not be enclosed in handlebars.
Expand source code Browse git
class TextReplace(XmlTransformBase): """ Replaces text in a template built into an Office Office Xml object that has been uploaded to the Presalytics Ooxml Automation service. Text to be should be in the format of a "template tag", which is a string enclosed in handlebars as such: '{{template_tag}}' Function Parameters Dictionary ---------- A dictionary that maps template tags to the new strings that will replace the tags in the rendered widget. The the dictionary keys should not be enclosed in handlebars. """ __xml_transform_name__ = "TextReplace" class TextElementInfo(object): """ Holds metadata for a list of `<t>` tags from an Office Open Xml document. """ def __init__(self, element: lxml.etree.Element, start_position: int): self.element = element self.text = "" if not element.text else element.text self.length = len(self.text) self.start_position = start_position self.end_position = self.start_position + self.length - 1 class TextList(object): """ Class for managing a list of `TextElementInfo` objects """ _list: typing.List['TextReplace.TextElementInfo'] def __init__(self): self._list = [] def append(self, text_element_info): if isinstance(text_element_info, globals()['TextReplace'].TextElementInfo): self._list.append(text_element_info) else: raise presalytics.lib.exceptions.InvalidArgumentException(message="Argument must be an instance of class 'TextElementInfo'") def set_text(self, index, new_string): self._list[index].text = new_string self._list[index].element.text = new_string def get_text(self, index): return self._list[index].text def get_plaintext_string(self): ret = "" for item in self._list: ret += item.text return ret def get_position(self, match_strings): for potential_match in match_strings: i = self.get_plaintext_string().find(potential_match) if i >= 0: break return i, potential_match def get_list_index_of_position(self, position: int): for item in self._list: if item.start_position <= position and item.end_position >= position: return self.get_index(item) raise presalytics.lib.exceptions.InvalidArgumentException(message="Position {} out of range".format(position)) def get_index(self, text_element_info: 'TextReplace.TextElementInfo'): for i in range(len(self._list)): if self._list[i] == text_element_info: return i raise presalytics.lib.exceptions.InvalidArgumentException(message="Supplied argument on in self._list") def set_text_to_empty_string(self, index): self._list[index].text = "" self._list[index].element.text = "" def set_start_to_new_value(self, index, new_value): start_text, remainder = self._list[index].text.split("{{", 1) end = "" if "}}" in remainder: end = remainder.split("}}", 1)[1] new_text = "{0}{1}{2}".format(start_text, new_value, end) self._list[index].text = new_text self._list[index].element.text = new_text def truncate_end(self, index): end_text = self._list[index].text.split("}}", 1)[-1] self._list[index].text = end_text self._list[index].element.text = end_text def plaintext_string_list(self): return [x.text for x in self._list] def reset(self): for i in range(0, len(self._list)): start_position = 0 if i == 0 else self._list[i-1].end_position + 1 self._list[i] = TextReplace.TextElementInfo(self._list[i].element, start_position) def replace_handlebars(self, info_list, params): """ Method that finds template tags and replaces them TODO: Upgrade to Liquid Syntax (or similar) """ for key, val in params.items(): match_keys = ["{{" + key + "}}", "{{ " + key + " }}"] match_start_position, match_key = info_list.get_position(match_keys) if match_start_position > -1: match_end_position = match_start_position + len(match_key) - 1 match_start_index = info_list.get_list_index_of_position(match_start_position) match_end_index = info_list.get_list_index_of_position(match_end_position) if match_start_index < match_end_index: info_list.truncate_end(match_end_index) for i in range(match_start_index + 1, match_end_index): info_list.set_text_to_empty_string(i) info_list.set_start_to_new_value(match_start_index, val) info_list.reset() self.replace_handlebars(info_list, params) def transform_function(self, lxml_element, params): """ Replaces template tags located {{inside_handlebars}} that match keys in the `params` dict with values from the `params` dict. This method searches for match for plain text strings, so that if template tags are split across xml `<t>` elements, they are still identified and replaced. Parameters ----------- lxml_element : lxml.etree.Element An element containing as least one an `<t>` element params : dict A dictionary that maps template tags (no handlebars) to their respective replacement values. """ text_list = lxml_element.findall('.//{*}t') info_list = TextReplace.TextList() position = 0 for tag in text_list: info = self.TextElementInfo(tag, position) position = info.end_position + 1 info_list.append(info) self.replace_handlebars(info_list, params) return lxml_element
Ancestors
- XmlTransformBase
- abc.ABC
Class variables
var TextElementInfo
-
Holds metadata for a list of
<t>
tags from an Office Open Xml document.Expand source code Browse git
class TextElementInfo(object): """ Holds metadata for a list of `<t>` tags from an Office Open Xml document. """ def __init__(self, element: lxml.etree.Element, start_position: int): self.element = element self.text = "" if not element.text else element.text self.length = len(self.text) self.start_position = start_position self.end_position = self.start_position + self.length - 1
var TextList
-
Class for managing a list of
TextElementInfo
objectsExpand source code Browse git
class TextList(object): """ Class for managing a list of `TextElementInfo` objects """ _list: typing.List['TextReplace.TextElementInfo'] def __init__(self): self._list = [] def append(self, text_element_info): if isinstance(text_element_info, globals()['TextReplace'].TextElementInfo): self._list.append(text_element_info) else: raise presalytics.lib.exceptions.InvalidArgumentException(message="Argument must be an instance of class 'TextElementInfo'") def set_text(self, index, new_string): self._list[index].text = new_string self._list[index].element.text = new_string def get_text(self, index): return self._list[index].text def get_plaintext_string(self): ret = "" for item in self._list: ret += item.text return ret def get_position(self, match_strings): for potential_match in match_strings: i = self.get_plaintext_string().find(potential_match) if i >= 0: break return i, potential_match def get_list_index_of_position(self, position: int): for item in self._list: if item.start_position <= position and item.end_position >= position: return self.get_index(item) raise presalytics.lib.exceptions.InvalidArgumentException(message="Position {} out of range".format(position)) def get_index(self, text_element_info: 'TextReplace.TextElementInfo'): for i in range(len(self._list)): if self._list[i] == text_element_info: return i raise presalytics.lib.exceptions.InvalidArgumentException(message="Supplied argument on in self._list") def set_text_to_empty_string(self, index): self._list[index].text = "" self._list[index].element.text = "" def set_start_to_new_value(self, index, new_value): start_text, remainder = self._list[index].text.split("{{", 1) end = "" if "}}" in remainder: end = remainder.split("}}", 1)[1] new_text = "{0}{1}{2}".format(start_text, new_value, end) self._list[index].text = new_text self._list[index].element.text = new_text def truncate_end(self, index): end_text = self._list[index].text.split("}}", 1)[-1] self._list[index].text = end_text self._list[index].element.text = end_text def plaintext_string_list(self): return [x.text for x in self._list] def reset(self): for i in range(0, len(self._list)): start_position = 0 if i == 0 else self._list[i-1].end_position + 1 self._list[i] = TextReplace.TextElementInfo(self._list[i].element, start_position)
Methods
def replace_handlebars(self, info_list, params)
-
Method that finds template tags and replaces them
TODO: Upgrade to Liquid Syntax (or similar)
Expand source code Browse git
def replace_handlebars(self, info_list, params): """ Method that finds template tags and replaces them TODO: Upgrade to Liquid Syntax (or similar) """ for key, val in params.items(): match_keys = ["{{" + key + "}}", "{{ " + key + " }}"] match_start_position, match_key = info_list.get_position(match_keys) if match_start_position > -1: match_end_position = match_start_position + len(match_key) - 1 match_start_index = info_list.get_list_index_of_position(match_start_position) match_end_index = info_list.get_list_index_of_position(match_end_position) if match_start_index < match_end_index: info_list.truncate_end(match_end_index) for i in range(match_start_index + 1, match_end_index): info_list.set_text_to_empty_string(i) info_list.set_start_to_new_value(match_start_index, val) info_list.reset() self.replace_handlebars(info_list, params)
def transform_function(self, lxml_element, params)
-
Replaces template tags located {{inside_handlebars}} that match keys in the
params
dict with values from theparams
dict.This method searches for match for plain text strings, so that if template tags are split across xml
<t>
elements, they are still identified and replaced.Parameters
lxml_element
:lxml.etree.Element
- An element containing as least one an
<t>
element params
:dict
- A dictionary that maps template tags (no handlebars) to their respective replacement values.
Expand source code Browse git
def transform_function(self, lxml_element, params): """ Replaces template tags located {{inside_handlebars}} that match keys in the `params` dict with values from the `params` dict. This method searches for match for plain text strings, so that if template tags are split across xml `<t>` elements, they are still identified and replaced. Parameters ----------- lxml_element : lxml.etree.Element An element containing as least one an `<t>` element params : dict A dictionary that maps template tags (no handlebars) to their respective replacement values. """ text_list = lxml_element.findall('.//{*}t') info_list = TextReplace.TextList() position = 0 for tag in text_list: info = self.TextElementInfo(tag, position) position = info.end_position + 1 info_list.append(info) self.replace_handlebars(info_list, params) return lxml_element
Inherited members
class MultiXmlTransform (transforms, fail_quietly=True, **kwargs)
-
This class allow users to run mutiple transforms on multiple targets in a single widget.
MultiXmlTransform
wraps multiple subclasses ofXmlTransformBase
, creates instances of them, and feeds them into anOoxmlEditorWidget
instance. To do this, theXmlTransformBase
subclasses must be loaded into theXML_TRANSFORM_REGISTRY
when called.The function parameters for the
Parameters
fail_quietly
:bool
, optional- Defaults to false.
Indicates whether an exception should be raised when a subclass specified
in the function parameters cannot not be found in the
XML_TRANSFORM_REGISTRY
instance.
Function Parameters Dictionary
transforms_list
:list
ofdict
-
A list of dictionaries, with each item in the list consiting of a dictionary of two entries.
The entries are as follows:-
name: [str] The name of the the subclass of
XmlTransformBase
that will be that will be initialized -
function_params: [dict] The
params
that will be the loaded into the instance'stransform_function
-
Expand source code Browse git
class MultiXmlTransform(XmlTransformBase): """ This class allow users to run mutiple transforms on multiple targets in a single widget. `MultiXmlTransform` wraps multiple subclasses of `presalytics.lib.widgets.ooxml_editors.XmlTransformBase`, creates instances of them, and feeds them into an `presalytics.lib.widgets.ooxml_editors.OoxmlEditorWidget` instance. To do this, the `presalytics.lib.widgets.ooxml_editors.XmlTransformBase` subclasses must be loaded into the `presalytics.lib.widgets.ooxml_editors.XML_TRANSFORM_REGISTRY` when called. The function parameters for the Parameters ---------- fail_quietly: bool, optional Defaults to false. Indicates whether an exception should be raised when a subclass specified in the function parameters cannot not be found in the `presalytics.lib.widgets.ooxml_editors.XML_TRANSFORM_REGISTRY` instance. Function Parameters Dictionary ---------- transforms_list: list of dict A list of dictionaries, with each item in the list consiting of a dictionary of two entries. The entries are as follows: * name: [str] The name of the the subclass of `presalytics.lib.widgets.ooxml_editors.XmlTransformBase` that will be that will be initialized * function_params: [dict] The `params` that will be the loaded into the instance's `transform_function` """ transform_instances: typing.List[XmlTransformBase] __xml_transform_name__ = "MultiXmlTransform" def __init__(self, transforms: typing.Dict[str, typing.List[typing.Dict[str, typing.Any]]], fail_quietly=True, **kwargs): super(MultiXmlTransform, self).__init__(transforms, **kwargs) transforms_list = transforms.get("transforms_list", None) self.fail_quietly = fail_quietly self.transform_instances = [] self.transform_registry = get_transform_registry() for _transform in transforms_list: #type: ignore key = "XmlTransform." + _transform["name"] transform_class = self.transform_registry.get(key) if not transform_class: message = "Could not find XmlTransform with name '{}'".format(_transform["name"]) if self.fail_quietly: logging.info(message) else: raise self.transform_registry.raise_error(message) #noqa else: inst = transform_class(_transform["function_params"]) self.transform_instances.append(inst) def transform_function(self, lxml_element, params): for inst in self.transform_instances: lxml_element = inst.execute(lxml_element) return lxml_element
Ancestors
- XmlTransformBase
- abc.ABC
Inherited members
class ApprovedExternalLinks (**kwargs)
-
StylePlugin
subclass for converting aPlugin
config into an html<link>
fragment.Attributes
attr_dict
:AttrDict
- Performs nested lookups on the
STYLES_MAP
Expand source code Browse git
class ApprovedExternalLinks(presalytics.lib.plugins.base.StylePlugin): """ `presalytics.lib.plugins.base.StylePlugin` subclass for converting a `presalytics.story.outline.Plugin` config into an html `<link>` fragment. Attributes ---------- attr_dict: presalytics.lib.plugins.external.AttrDict Performs nested lookups on the `STYLES_MAP` """ __plugin_name__ = 'external_links' STYLES_MAP = { 'reveal': { 'base': '{0}/static/vendor/reveal/reveal.css'.format(site_host), 'themes': { 'beige': '{0}/static/vendor/reveal/theme/beige.css'.format(site_host), 'black': '{0}/static/vendor/reveal/theme/black.css'.format(site_host), 'blood': '{0}/static/vendor/reveal/theme/blood.css'.format(site_host), 'league': '{0}/static/vendor/reveal/theme/league.css'.format(site_host), 'moon': '{0}/static/vendor/reveal/theme/moon.css'.format(site_host), 'night': '{0}/static/vendor/reveal/theme/night.css'.format(site_host), 'serif': '{0}/static/vendor/reveal/theme/serif.css'.format(site_host), 'simple': '{0}/static/vendor/reveal/theme/simple.css'.format(site_host), 'sky': '{0}/static/vendor/reveal/theme/sky.min.css'.format(site_host), 'solarized': '{0}/static/vendor/reveal/theme/solarized.css'.format(site_host), 'white': '{0}/static/vendor/reveal/theme/white.css'.format(site_host) }, 'print': 'https://cdnjs.cloudflare.com/ajax/libs/reveal.js/3.6.0/css/print/pdf.min.css', 'customizations': '{0}/static/css/reveal-customizations.css'.format(site_host), 'toolbar': '{0}/static/css/toolbar.css'.format(site_host), }, 'preloaders' : '{0}/static/css/preloaders.css'.format(site_host), 'bootstrap4': "https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css", 'font-awesome': '{0}/static/vendor/fontawesome-free/css/all.min.css'.format(site_host), } """ Static nested dictionary containing links to external stylesheets that will be rendered alongside this plugin """ def __init__(self, **kwargs): super(ApprovedExternalLinks, self).__init__(**kwargs) self.attr_dict = AttrDict(self.STYLES_MAP) def to_style(self, config, **kwargs): """ Converts a dot-notation key for nested dictionaries (e.g., 'reveal.base') into a string contain an html fragement with `<link>` tag. The dot-notation key is pulled from the 'approved_styles_key' of the 'config' attrubte of subclass of a `presalytics.story.outline.Plugin` object. """ key = config['approved_styles_key'] link = self.attr_dict.flatten()[key] if link is None: message = "Key {0} does not reference a link in the APPROVED_STYLES dictionary".format(key) raise presalytics.lib.exceptions.MissingConfigException(message) return '<link rel="stylesheet" href="{0}"/>'.format(link)
Ancestors
- StylePlugin
- PluginBase
- abc.ABC
Class variables
var STYLES_MAP
-
Static nested dictionary containing links to external stylesheets that will be rendered alongside this plugin
Methods
def to_style(self, config, **kwargs)
-
Converts a dot-notation key for nested dictionaries (e.g., 'reveal.base') into a string contain an html fragement with
<link>
tag. The dot-notation key is pulled from the 'approved_styles_key' of the 'config' attrubte of subclass of aPlugin
object.Expand source code Browse git
def to_style(self, config, **kwargs): """ Converts a dot-notation key for nested dictionaries (e.g., 'reveal.base') into a string contain an html fragement with `<link>` tag. The dot-notation key is pulled from the 'approved_styles_key' of the 'config' attrubte of subclass of a `presalytics.story.outline.Plugin` object. """ key = config['approved_styles_key'] link = self.attr_dict.flatten()[key] if link is None: message = "Key {0} does not reference a link in the APPROVED_STYLES dictionary".format(key) raise presalytics.lib.exceptions.MissingConfigException(message) return '<link rel="stylesheet" href="{0}"/>'.format(link)
Inherited members
class ApprovedExternalScripts (**kwargs)
-
ScriptPlugin
subclass for converting aPlugin
config into an html<script>
fragment.Attributes
attr_dict
:AttrDict
- Performs nested lookups on the
SCRIPT_MAP
Expand source code Browse git
class ApprovedExternalScripts(presalytics.lib.plugins.base.ScriptPlugin): """ `presalytics.lib.plugins.base.ScriptPlugin` subclass for converting a `presalytics.story.outline.Plugin` config into an html `<script>` fragment. Attributes ---------- attr_dict: presalytics.lib.plugins.external.AttrDict Performs nested lookups on the `SCRIPT_MAP` """ __plugin_name__ = 'external_scripts' SCRIPT_MAP = { 'd3': 'https://d3js.org/d3.v5.min.js', 'd3v3': 'https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js', 'reveal': { 'base': '{0}/static/vendor/reveal/reveal.js'.format(site_host), # 'markdown': 'https://cdnjs.cloudflare.com/ajax/libs/reveal.js/3.6.0/plugin/markdown/markdown.min.js', # 'highlight': 'https://cdnjs.cloudflare.com/ajax/libs/reveal.js/3.6.0/plugin/highlight/highlight.min.js', # 'math': 'https://cdnjs.cloudflare.com/ajax/libs/reveal.js/3.6.0/plugin/math/math.min.js', # 'zoom': 'https://cdnjs.cloudflare.com/ajax/libs/reveal.js/3.6.0/plugin/zoom-js/zoom.min.js', # 'notes': 'https://cdnjs.cloudflare.com/ajax/libs/reveal.js/3.6.0/plugin/notes/notes.min.js', # 'print': 'https://cdnjs.cloudflare.com/ajax/libs/reveal.js/3.6.0/plugin/print-pdf/print-pdf.min.js', 'customizations': '{0}/static/js/revealcustomizations.js'.format(site_host), 'toolbar': '{0}/static/js/toolbar.js'.format(site_host), 'screenfull': '{0}/static/vendor/screenfull/screenfull.min.js'.format(site_host), }, 'mpld3': '{0}/static/mpld3/mpld3.min.js'.format(site_host), 'ooxml': '{0}/static/ooxml/ooxml.js'.format(site_host), 'mpl-responsive': '{0}/static/js/mpl-responsive.js'.format(site_host), 'jquery': 'https://code.jquery.com/jquery-3.4.1.min.js', 'popper': 'https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js', 'bootstrap4': 'https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js' } """ Static nested dictionary containing links to external scripts that will be rendered alongside this plugin """ def __init__(self, **kwargs): super(ApprovedExternalScripts, self).__init__(**kwargs) self.attr_dict = AttrDict(self.SCRIPT_MAP) def to_script(self, config, **kwargs): """ Converts a dot-notation key for nested dictionaries (e.g., 'reveal.base') into a string containing an html fragement with `<script>` tags. The dot-notation key is pulled from the 'approved_styles_key' of the 'config' attrubte of subclass of a `presalytics.story.outline.Plugin` object. """ key = config['approved_scripts_key'] link = self.attr_dict.flatten()[key] if link is None: message = "Key {0} does not reference a link in the APPROVED_SCRIPTS dictionary".format(key) raise presalytics.lib.exceptions.MissingConfigException(message) return '<script type="text/javascript" src="{0}"></script>'.format(link)
Ancestors
- ScriptPlugin
- PluginBase
- abc.ABC
Class variables
var SCRIPT_MAP
-
Static nested dictionary containing links to external scripts that will be rendered alongside this plugin
Methods
def to_script(self, config, **kwargs)
-
Converts a dot-notation key for nested dictionaries (e.g., 'reveal.base') into a string containing an html fragement with
<script>
tags. The dot-notation key is pulled from the 'approved_styles_key' of the 'config' attrubte of subclass of aPlugin
object.Expand source code Browse git
def to_script(self, config, **kwargs): """ Converts a dot-notation key for nested dictionaries (e.g., 'reveal.base') into a string containing an html fragement with `<script>` tags. The dot-notation key is pulled from the 'approved_styles_key' of the 'config' attrubte of subclass of a `presalytics.story.outline.Plugin` object. """ key = config['approved_scripts_key'] link = self.attr_dict.flatten()[key] if link is None: message = "Key {0} does not reference a link in the APPROVED_SCRIPTS dictionary".format(key) raise presalytics.lib.exceptions.MissingConfigException(message) return '<script type="text/javascript" src="{0}"></script>'.format(link)
Inherited members
class LocalStylesPlugin (**kwargs)
-
Plugin incorporate styles from a css stylesheet in a local filepath
Expand source code Browse git
class LocalStylesPlugin(presalytics.lib.plugins.base.StylePlugin): """ Plugin incorporate styles from a css stylesheet in a local filepath """ __plugin_name__ = 'local' LOCAL_STYLES_MAP = { "single_item_grid": os.path.join(css_path, "single-item-grid.css"), "flex_row": os.path.join(css_path, "flex-row.css"), "light_grey": os.path.join(css_path, "light-grey.css"), "responsive_title": os.path.join(css_path, "responsive-title.css"), "reveal_overrides": os.path.join(css_path, "reveal-overrides-base.css") } """ Dictionary containing a map configuration keys to css files that are member of the Presalytics Python Library manifest """ def to_style(self, config, **kwargs): """ Renders a css stylesheet as an inline style tag in a story Parameters ---------- config : dict A dictionary containing either a "css_file_path" key, which points to a local file path, or a "css_file_id" refers to a css file included in the Presalytics Python Library's manifest """ id = config.get("css_file_id", None) if not id: file_path = config.get("css_file_path", None) if not file_path: message = 'Plugin requires "css_file_id" or "css_file_path" in its configuration dictionary' raise presalytics.lib.exceptions.MissingConfigException(message) if not os.path.exists(file_path): message = 'Path {0} in plugin configuration does not exist'.format(file_path) raise presalytics.lib.exceptions.MissingConfigException(message) else: file_path = LocalStylesPlugin.LOCAL_STYLES_MAP[id] with open(file_path, 'r') as f: style_data = f.read() tag = "<style>{0}</style>".format(style_data) return tag
Ancestors
- StylePlugin
- PluginBase
- abc.ABC
Class variables
var LOCAL_STYLES_MAP
-
Dictionary containing a map configuration keys to css files that are member of the Presalytics Python Library manifest
Methods
def to_style(self, config, **kwargs)
-
Renders a css stylesheet as an inline style tag in a story
Parameters
config
:dict
- A dictionary containing either a "css_file_path" key, which points to a local file path, or a "css_file_id" refers to a css file included in the Presalytics Python Library's manifest
Expand source code Browse git
def to_style(self, config, **kwargs): """ Renders a css stylesheet as an inline style tag in a story Parameters ---------- config : dict A dictionary containing either a "css_file_path" key, which points to a local file path, or a "css_file_id" refers to a css file included in the Presalytics Python Library's manifest """ id = config.get("css_file_id", None) if not id: file_path = config.get("css_file_path", None) if not file_path: message = 'Plugin requires "css_file_id" or "css_file_path" in its configuration dictionary' raise presalytics.lib.exceptions.MissingConfigException(message) if not os.path.exists(file_path): message = 'Path {0} in plugin configuration does not exist'.format(file_path) raise presalytics.lib.exceptions.MissingConfigException(message) else: file_path = LocalStylesPlugin.LOCAL_STYLES_MAP[id] with open(file_path, 'r') as f: style_data = f.read() tag = "<style>{0}</style>".format(style_data) return tag
Inherited members
class OoxmlTheme (**kwargs)
-
This class takes theme data from an Presalytics API Ooxml Automation service Theme object and feeds those values into the
RevealCustomTheme
Expand source code Browse git
class OoxmlTheme(presalytics.lib.plugins.reveal_theme.RevealCustomTheme): """ This class takes theme data from an Presalytics API Ooxml Automation service Theme object and feeds those values into the `presalytics.lib.plugins.reveal_theme.RevealCustomTheme` """ __plugin_name__ = 'ooxml-theme' def to_style(self, config, **kwargs): base_path = os.path.join(os.path.dirname(__file__), 'scss') if os.name == "nt": import_path = posixpath.join(*base_path.split('\\')) else: import_path = base_path scss_file = os.path.join(base_path, 'overrides.tmpl') config.update({'path': import_path}) with open(scss_file, 'r') as file: scss_template_string = file.read() config = self.config_to_camelCase(config) scss_string = scss_template_string.format(**config) new_css = sass.compile(string=scss_string) style_string = "<style>\n{0}\n</style>".format(new_css) font_names = [config["headingFont"], config["bodyFont"]] links = self.get_fonts(font_names) return links + style_string def config_to_camelCase(self, config: typing.Dict) -> typing.Dict: new_dict = dict() for key, val in config.items(): new_dict.update({ presalytics.story.util.to_camel_case(key): val }) return new_dict
Ancestors
- RevealCustomTheme
- StylePlugin
- PluginBase
- abc.ABC
Methods
def config_to_camelCase(self, config)
-
Expand source code Browse git
def config_to_camelCase(self, config: typing.Dict) -> typing.Dict: new_dict = dict() for key, val in config.items(): new_dict.update({ presalytics.story.util.to_camel_case(key): val }) return new_dict
Inherited members
class RevealConfigPlugin (**kwargs)
-
Allows users configure reveal.js initialization settings for when their story is rendered via
Revealer
For more information regarding reveal.js' initialization settings, please refer to https://github.com/hakimel/reveal.js/#configuration
Expand source code Browse git
class RevealConfigPlugin(ext.ScriptPlugin, jinja.JinjaPluginMakerMixin): """ Allows users configure reveal.js initialization settings for when their story is rendered via `presalytics.story.revealer.Revealer` For more information regarding reveal.js' initialization settings, please refer to https://github.com/hakimel/reveal.js/#configuration """ __plugin_name__ = 'reveal' __dependencies__ = [ { 'kind': 'script', 'name': 'external_scripts', 'config': { 'approved_scripts_key': 'reveal.base' } }, { 'kind': 'style', 'name': 'external_links', 'config': { 'approved_styles_key': 'reveal.base' } }, { 'kind': 'script', 'name': 'external_scripts', 'config': { 'approved_scripts_key': 'reveal.screenfull' } }, { 'kind': 'style', 'name': 'external_links', 'config': { 'approved_styles_key': 'reveal.toolbar' } }, { 'kind': 'style', 'name': 'external_links', 'config': { 'approved_styles_key': 'font-awesome' } }, { 'kind': 'script', 'name': 'external_scripts', 'config': { 'approved_scripts_key': 'reveal.toolbar' } }, { 'kind': 'style', 'name': 'external_links', 'config': { 'approved_styles_key': 'reveal.themes.white' } }, { 'kind': 'style', 'name': 'external_links', 'config': { 'approved_styles_key': 'reveal.customizations' } }, { 'kind': 'script', 'name': 'external_scripts', 'config': { 'approved_scripts_key': 'reveal.customizations' } }, ] def to_script(self, config, **kwargs): reveal_config = self.default_config if config.get("reveal_params", None): reveal_config.update(config.pop("reveal_params")) config.update({'reveal_config': reveal_config}) return self.render(config) template = """ <script type="text/javascript"> window.addEventListener('toolbar-initialized', function(e) { var config = {{ reveal_config|tojson(indent=4) }}; config.plugins = [window.presalyticsToolbar]; Reveal.initialize(config); }); </script> """ """ The `<script>` fragment that get populated with a initialization data via the `presalytics.lib.plugins.jinja.JinjaPluginMakerMixin` """ default_config = { 'controls': True, # Display presentation control arrows # Help the user learn the controls by providing hints, for example by # bouncing the down arrow when they first encounter a vertical slide 'controlsTutorial': True, # Determines where controls appear, "edges" or "bottom-right" # 'controlsLayout': 'bottom-right', # Visibility rule for backwards navigation arrows; "faded", "hidden" # or "visible" 'controlsBackArrows': 'faded', # Display a presentation progress bar 'progress': True, # Display the page number of the current slide 'slideNumber': False, # Add the current slide number to the URL hash so that reloading the #page/copying the URL will return you to the same slide 'hash': False, # Push each slide change to the browser history. Implies `hash: true` 'history': False, # Enable keyboard shortcuts for navigation 'keyboard': True, # Enable the slide overview mode 'overview': True, # Vertical centering of slides 'center': True, # Enables touch navigation on devices with touch input 'touch': True, # Loop the presentation 'loop': True, # Change the presentation direction to be RTL 'rtl': False, # See https://github.com/hakimel/reveal.js/#navigation-mode 'navigationMode': 'default', # Randomizes the order of slides each time the presentation loads 'shuffle': False, # Turns fragments on and off globally 'fragments': True, # Flags whether to include the current fragment in the URL, # so that reloading brings you to the same fragment position #} 'fragmentInURL': False, # Flags if the presentation is running in an embedded mode, # i.e. contained within a limited portion of the screen #} 'embedded': False, # Flags if we should show a help overlay when the questionmark # key is pressed #} 'help': True, # Flags if speaker notes should be visible to all viewers 'showNotes': False, # Global override for autoplaying embedded media (video/audio/iframe) # - null: Media will only autoplay if data-autoplay is present # - true: All media will autoplay, regardless of individual setting # - false: No media will autoplay, regardless of individual setting 'autoPlayMedia': 'null', # Global override for preloading lazy-loaded iframes # - null: Iframes with data-src AND data-preload will be loaded when within # the viewDistance, iframes with only data-src will be loaded when visible # - true: All iframes with data-src will be loaded when within the viewDistance # - false: All iframes with data-src will be loaded only when visible #} 'preloadIframes': 'null', # Number of milliseconds between automatically proceeding to the # next slide, disabled when set to 0, this value can be overwritten # by using a data-autoslide attribute on your slides #} 'autoSlide': 0, # Stop auto-sliding after user input 'autoSlideStoppable': True, # Use this method for navigation when auto-sliding 'autoSlideMethod': 'Reveal.navigateNext', # Specify the average time in seconds that you think you will spend # presenting each slide. This is used to show a pacing timer in the # speaker view 'defaultTiming': 120, # Enable slide navigation via mouse wheel 'mouseWheel': False, # Hide cursor if inactive 'hideInactiveCursor': True, # Time before the cursor is hidden (in ms) 'hideCursorTime': 5000, # Hides the address bar on mobile devices 'hideAddressBar': True, # Opens links in an iframe preview overlay # Add `data-preview-link` and `data-preview-link="false"` to customise each link # individually 'previewLinks': False, # Transition style 'transition': 'slide', # none/fade/slide/convex/concave/zoom # Transition speed 'transitionSpeed': 'default', # default/fast/slow # Transition style for full page slide backgrounds 'backgroundTransition': 'fade', # none/fade/slide/convex/concave/zoom # Number of slides away from the current that are visible 'viewDistance': 3, # Parallax background image 'parallaxBackgroundImage': '', # e.g. "'https://s3.amazonaws.com/hakim-static/reveal-js/reveal-parallax-1.jpg'" # Parallax background size 'parallaxBackgroundSize': '', # CSS syntax, e.g. "2100px 900px" # Number of pixels to move the parallax background per slide # - Calculated automatically unless specified # - Set to 0 to disable movement along an axis 'parallaxBackgroundHorizontal': 'null', 'parallaxBackgroundVertical': 'null', # The display mode that will be used to show slides 'display': 'block', # Allow for responsive presenation formats 'width': "100%", 'height': "100%", 'margin': 0, 'minScale': 1, 'maxScale': 1, # Presalytics toolbar 'showToolbar': True, 'toolbar': { # Specifies where the toolbar will be shown: 'top' or 'bottom' 'position': 'bottom', # Add button to toggle fullscreen mode for the presentation 'fullscreen': True, # Add button to toggle the overview mode on and off 'overview': True, # Add button to pause (hide) the presentation display 'pause': True, # Add button to show the speaker notes 'notes': False, # Add button to show the help overlay 'help': False, # If true, the reveal.js-menu will be moved into the toolbar. # Set to false to leave the menu on its own. 'captureMenu': True, # If true, the playback control will be moved into the toolbar. # This is only relevant if the presentation is configured to autoSlide. # Set to false to leave the menu on its own. 'capturePlaybackControl': True, # By default the menu will load it's own font-awesome library # icons. If your presentation needs to load a different # font-awesome library the 'loadIcons' option can be set to false # and the menu will not attempt to load the font-awesome library. 'loadIcons': True, # Instructs the toolbar to load the Presaytics chat interface 'chat': True, # Shows the make pdf button 'pdf': True, } } """ A dictionary containing default values for rendering presalytics stories """
Ancestors
Class variables
var template
-
The
<script>
fragment that get populated with a initialization data via theJinjaPluginMakerMixin
var default_config
-
A dictionary containing default values for rendering presalytics stories
Inherited members
class RevealCustomTheme (**kwargs)
-
Plugin to for customizing reveal.js settings for a given story
This plugin's configuration dictionary keys and that load them in as variable to a _variables.scss file and compiles the scss using the sass python package.
See reveal.js' Creating a Theme page for more information regarding configuration values.
Expand source code Browse git
class RevealCustomTheme(presalytics.lib.plugins.base.StylePlugin): """ Plugin to for customizing reveal.js settings for a given story This plugin's configuration dictionary keys and that load them in as variable to a _variables.scss file and compiles the scss using the sass python package. See reveal.js' [Creating a Theme](https://github.com/hakimel/reveal.js/blob/8a54118f43b91030f3965088d5e1c1c7598a5cd3/css/theme/README.md) page for more information regarding configuration values. """ __plugin_name__ = 'reveal_custom_theme' defaults: typing.Dict[str, str] fonts_base_url = 'https://fonts.googleapis.com/css?family={0}' """ Google fonts base url for loading fonts via a `<link>` tag """ def to_style(self, config, **kwargs): """ Returns compiled scss and links to download fonts """ scss_variables = self.defaults scss_variables.update(config) scss_string = self.get_base_scss(scss_variables) new_css = sass.compile(string=scss_string) style_string = "<style>\n{0}\n</style>".format(new_css) links = self.get_fonts(config["fonts"]) return links + style_string def get_fonts(self, fonts: typing.List[str]): """ Creates `<link>` tags from font names """ links = "" for font in fonts: link = self.get_font_link(font) if link: links = links + link + "\n" return links def get_font_link(self, font_name) -> typing.Optional[str]: """ Tests whether a given font is available for download form google fonts. Returns the link tag if available """ test_url = self.fonts_base_url.format(font_name) r = requests.get(test_url) if r.status_code == 200: return '<link href="{0}" rel="stylesheet">'.format(test_url) else: return None def get_base_scss(self, scss_variables): """ loads the reveal.js scss files into string to be compiled by the sass module """ scss_folder = os.path.join(os.path.dirname(__file__), "scss") mixins_file = os.path.join(scss_folder, "reveal-mixins.scss") settings_file = os.path.join(scss_folder, "reveal-settings.scss") theme_file = os.path.join(scss_folder, "reveal-theme.scss") overrides_file = os.path.join(scss_folder, "overrides.scss.tmpl") with open(mixins_file, 'r') as m: mixins = m.read() with open(settings_file, 'r') as s: settings = s.read() with open(theme_file, 'r') as t: theme = t.read() with open(overrides_file, 'r') as o: overrides_template = o.read() overrides = overrides_template.format(**scss_variables) scss_string = "{0}\n{1}\n{2}\n{3}".format(mixins, settings, overrides, theme) return scss_string # Defaults to white theme parameters defaults = { # Background of the presentation "background_color": "#2b2b2b", # Primary/body text "main_font": 'Lato', "main_font_size": "inherit", "main_color": "#eee", # Vertical spacing between blocks of text "block_margin": "20px", # Headings "heading_margin": "0 0 $blockMargin 0", "heading_font": 'Lato', "heading_color": "#eee", "heading_line_height": "1.2", "heading_letter_spacing": "normal", "heading_text_transform": "none", "heading_text_shadow": "none", "heading_font_weight": "normal", "heading_one_text_shadow": "$headingTextShadow", "heading_one_size": "3.77em", "heading_two_size": "2.11em", "heading_three_size": "1.55em", "heading_four_size": "1.00em", "code_font": "monospace", # Links and actions "link_color": "#13DAEC", "linkColorHover": "lighten( $linkColor, 20% )", # Text selection "selection_background_color": "#FF5E99", "selection_color": "#fff" } """ Default reveal.js theme configuration for presalytics stories """
Ancestors
- StylePlugin
- PluginBase
- abc.ABC
Subclasses
Class variables
var fonts_base_url
-
Google fonts base url for loading fonts via a
<link>
tag var defaults
-
Default reveal.js theme configuration for presalytics stories
Methods
def to_style(self, config, **kwargs)
-
Returns compiled scss and links to download fonts
Expand source code Browse git
def to_style(self, config, **kwargs): """ Returns compiled scss and links to download fonts """ scss_variables = self.defaults scss_variables.update(config) scss_string = self.get_base_scss(scss_variables) new_css = sass.compile(string=scss_string) style_string = "<style>\n{0}\n</style>".format(new_css) links = self.get_fonts(config["fonts"]) return links + style_string
def get_fonts(self, fonts)
-
Creates
<link>
tags from font namesExpand source code Browse git
def get_fonts(self, fonts: typing.List[str]): """ Creates `<link>` tags from font names """ links = "" for font in fonts: link = self.get_font_link(font) if link: links = links + link + "\n" return links
def get_font_link(self, font_name)
-
Tests whether a given font is available for download form google fonts. Returns the link tag if available
Expand source code Browse git
def get_font_link(self, font_name) -> typing.Optional[str]: """ Tests whether a given font is available for download form google fonts. Returns the link tag if available """ test_url = self.fonts_base_url.format(font_name) r = requests.get(test_url) if r.status_code == 200: return '<link href="{0}" rel="stylesheet">'.format(test_url) else: return None
def get_base_scss(self, scss_variables)
-
loads the reveal.js scss files into string to be compiled by the sass module
Expand source code Browse git
def get_base_scss(self, scss_variables): """ loads the reveal.js scss files into string to be compiled by the sass module """ scss_folder = os.path.join(os.path.dirname(__file__), "scss") mixins_file = os.path.join(scss_folder, "reveal-mixins.scss") settings_file = os.path.join(scss_folder, "reveal-settings.scss") theme_file = os.path.join(scss_folder, "reveal-theme.scss") overrides_file = os.path.join(scss_folder, "overrides.scss.tmpl") with open(mixins_file, 'r') as m: mixins = m.read() with open(settings_file, 'r') as s: settings = s.read() with open(theme_file, 'r') as t: theme = t.read() with open(overrides_file, 'r') as o: overrides_template = o.read() overrides = overrides_template.format(**scss_variables) scss_string = "{0}\n{1}\n{2}\n{3}".format(mixins, settings, overrides, theme) return scss_string
Inherited members
class JinjaTemplateBuilder (page, **kwargs)
-
Base class for building objects that render html from
Page
objects that implementing the Jinja2 python library.Instances of this class will look for templates located at file location identified by the
__template_file__
attribute. If not template is found, the content in thetemplate_string
attribute will be used. Templates should call each widget'sto_html()
method in a placeholder in order to generate valid html for the page.About building templates: Templates are passed a
widgets
attribute and awidget_index
integer (initialized at 0) as part of the context during rendering,. To render multiple widgets on a page, the following pattern can be used inside of templates to increment through the widgets as the jinja2 rendering engine moves through the template:{{ widgets[widget_index.next()].to_html() }} // renders widget and increments widget_index
Please also note that if
<script>
tags are included in the template, they will be stripped out downstream by aRenderer
for security reasons. Scripts included in templates will not make it to the browser.Parameters
page
:Page
- The page to be rendered
Attributes
__template_file__
:str
- The filename to an html file containing a fragment that will be rendered into
a page by a
Renderer
__template_paths__
:list
ofstr
- user-defined filepaths to directories where Jinja2 should look for the
__template_file__
__css__
:list
ofstr
- Each str in this list is a key that maps to an entry in the
LocalStylesPlugin.LOCAL_STYLES_MAP
. Ids matched here load the css files as a dependent plugin. template_paths
:list
ofstr
- The folders Jinja2 will look in. Includes that html directory adjacent to this
__file__
, appending with the files in the__template_paths__
widgets
:list
ofsubclass
instances
ofWidgetBase
- Widget to be rendered into the placeholders in the template identified by
__template_file__
Expand source code Browse git
class JinjaTemplateBuilder(presalytics.story.components.PageTemplateBase): """ Base class for building objects that render html from `presalytics.story.outline.Page` objects that implementing the [Jinja2](https://jinja.palletsprojects.com/) python library. Instances of this class will look for templates located at file location identified by the `__template_file__` attribute. If not template is found, the content in the `template_string` attribute will be used. Templates should call each widget's `to_html()` method in a placeholder in order to generate valid html for the page. *About building templates*: Templates are passed a `widgets` attribute and a `widget_index` integer (initialized at 0) as part of the context during rendering,. To render multiple widgets on a page, the following pattern can be used inside of templates to increment through the widgets as the jinja2 rendering engine moves through the template: {{ widgets[widget_index.next()].to_html() }} // renders widget and increments widget_index Please also note that if `<script>` tags are included in the template, they will be stripped out downstream by a `presalytics.story.components.Renderer` for security reasons. Scripts included in templates will not make it to the browser. Parameters ---------- page : presalytics.story.outline.Page The page to be rendered Attributes ---------- __template_file__ : str The filename to an html file containing a fragment that will be rendered into a page by a `presalytics.story.components.Renderer` __template_paths__ : list of str user-defined filepaths to directories where Jinja2 should look for the `__template_file__` __css__ : list of str Each str in this list is a key that maps to an entry in the `presalytics.lib.plugins.local.LocalStylesPlugin.LOCAL_STYLES_MAP`. Ids matched here load the css files as a dependent plugin. template_paths : list of str The folders Jinja2 will look in. Includes that html directory adjacent to this `__file__`, appending with the files in the `__template_paths__` widgets : list of subclass instances of presalytics.story.components.WidgetBase Widget to be rendered into the placeholders in the template identified by `__template_file__` """ __css__: typing.Sequence[str] __template_file__: str __template_paths__: typing.List[str] template_paths: typing.List[str] __template_paths__ = [] __css__ = [] class WidgetIndexer(object): """ A counter class for call `widgetindex.next()` in html templates to move this the widget list """ def __init__(self): self._val = 0 def next(self): """ Returns the current widget index and increments the value for the next call """ cur = self._val self._val += 1 return cur def __init__(self, page: 'Page', **kwargs) -> None: super().__init__(page, **kwargs) pkg_templates = os.path.join(os.path.dirname(__file__), "html") self.template_paths = [pkg_templates, os.getcwd()] if len(self.__template_paths__) > 0: self.template_paths[0:0] = self.__template_paths__ self.is_template_local = self.check_for_file() if self.is_template_local: self.template_string = self.read_template_string() elif self.outline_page.additional_properties.get("template_string", None): self.template_string = self.outline_page.additional_properties.get("template_string") else: self.template_string = None if len(kwargs.keys()) > 0: self.outline_page.additional_properties.update(kwargs) @classmethod def deserialize(cls, component, **kwargs): return cls(component, **kwargs) @util.classproperty def __plugins__(cls): plugin_list = [] for id in cls.__css__: new_item = { 'kind': 'style', 'name': 'local', 'config': { 'css_file_id': id } } plugin_list.append(new_item) return plugin_list def serialize(self): updated_plugins = [] for plugin_data in self.__plugins__: updated_plugins.append(presalytics.story.outline.Plugin(**plugin_data)) self.outline_page.plugins = updated_plugins if self.template_string: self.outline_page.additional_properties["template_string"] = self.template_string return self.outline_page def get_template_name(self): """ Requires subclasses have either a `__template_file__` class property, or override this method """ if self.__template_file__: return self.__template_file__ else: raise NotImplementedError def render(self, **kwargs): """ Renders the widgets to html """ if self.is_template_local: return self.render_from_file(**kwargs) elif self.template_string: return self.render_from_backup_string(**kwargs) else: raise presalytics.lib.exceptions.MissingConfigException("Missing __template_file__: {}".format(self.get_template_name())) def _make_context(self): context = { "widgets": self.widgets, "widget_index": self.WidgetIndexer() } if self.outline_page.additional_properties: context.update(self.outline_page.additional_properties) context.pop("template_string", None) return context def render_from_file(self, **kwargs) -> str: """ Returns rendered html with widgets rendered into template placeholders """ template = self.load_jinja_template() context = self._make_context() return template.render(**context) def render_from_backup_string(self, **kwargs): """ Returns rendered html with widgets rendered into template placeholders """ context = self._make_context() options = { "loader": jinja2.BaseLoader() } if context.get("jinja_options", None): options.update(context.pop("jinja_options")) env = jinja2.Environment(**options) env.filters['htmlize'] = htmlize template = env.from_string(self.template_string) return template.render(**context) def read_template_string(self) -> typing.Optional[str]: """ Finds the template file and reads it into a string """ for _dir in self.template_paths: fpath = os.path.join(_dir, self.get_template_name()) if os.path.exists(fpath): with open(fpath, 'r') as f: template_string = f.read() return template_string return None def load_jinja_template(self) -> jinja2.Template: """ Uses the fileloader to load a local template file into the jinja2 environment """ loader = jinja2.FileSystemLoader(self.template_paths) env = jinja2.Environment(loader=loader) env.filters['htmlize'] = htmlize return env.get_template(self.get_template_name()) def check_for_file(self): """ Checks whether `__template_file__` exists locally """ for _dir in self.template_paths: fpath = os.path.join(_dir, self.get_template_name()) if os.path.exists(fpath): return True return False
Ancestors
- PageTemplateBase
- ComponentBase
- abc.ABC
Subclasses
Class variables
var WidgetIndexer
-
A counter class for
call
widgetindex.next()
in html templates to move this the widget listExpand source code Browse git
class WidgetIndexer(object): """ A counter class for call `widgetindex.next()` in html templates to move this the widget list """ def __init__(self): self._val = 0 def next(self): """ Returns the current widget index and increments the value for the next call """ cur = self._val self._val += 1 return cur
Methods
def get_template_name(self)
-
Requires subclasses have either a
__template_file__
class property, or override this methodExpand source code Browse git
def get_template_name(self): """ Requires subclasses have either a `__template_file__` class property, or override this method """ if self.__template_file__: return self.__template_file__ else: raise NotImplementedError
def render(self, **kwargs)
-
Renders the widgets to html
Expand source code Browse git
def render(self, **kwargs): """ Renders the widgets to html """ if self.is_template_local: return self.render_from_file(**kwargs) elif self.template_string: return self.render_from_backup_string(**kwargs) else: raise presalytics.lib.exceptions.MissingConfigException("Missing __template_file__: {}".format(self.get_template_name()))
def render_from_file(self, **kwargs)
-
Returns rendered html with widgets rendered into template placeholders
Expand source code Browse git
def render_from_file(self, **kwargs) -> str: """ Returns rendered html with widgets rendered into template placeholders """ template = self.load_jinja_template() context = self._make_context() return template.render(**context)
def render_from_backup_string(self, **kwargs)
-
Returns rendered html with widgets rendered into template placeholders
Expand source code Browse git
def render_from_backup_string(self, **kwargs): """ Returns rendered html with widgets rendered into template placeholders """ context = self._make_context() options = { "loader": jinja2.BaseLoader() } if context.get("jinja_options", None): options.update(context.pop("jinja_options")) env = jinja2.Environment(**options) env.filters['htmlize'] = htmlize template = env.from_string(self.template_string) return template.render(**context)
def read_template_string(self)
-
Finds the template file and reads it into a string
Expand source code Browse git
def read_template_string(self) -> typing.Optional[str]: """ Finds the template file and reads it into a string """ for _dir in self.template_paths: fpath = os.path.join(_dir, self.get_template_name()) if os.path.exists(fpath): with open(fpath, 'r') as f: template_string = f.read() return template_string return None
def load_jinja_template(self)
-
Uses the fileloader to load a local template file into the jinja2 environment
Expand source code Browse git
def load_jinja_template(self) -> jinja2.Template: """ Uses the fileloader to load a local template file into the jinja2 environment """ loader = jinja2.FileSystemLoader(self.template_paths) env = jinja2.Environment(loader=loader) env.filters['htmlize'] = htmlize return env.get_template(self.get_template_name())
def check_for_file(self)
-
Checks whether
__template_file__
exists locallyExpand source code Browse git
def check_for_file(self): """ Checks whether `__template_file__` exists locally """ for _dir in self.template_paths: fpath = os.path.join(_dir, self.get_template_name()) if os.path.exists(fpath): return True return False
Inherited members
class WidgetBase (name, *args, **kwargs)
-
Inherit from this base class to create widget components that can be rendered to html via the
Renderer
class. This component also needs to build a method that allows the widget to be serialzed into aWidget
object.Parameters
widget
:Widget
- A
Widget
object use for initialized the component class.
Attributes
outline_widget
:Widget
- A
Widget
object
Expand source code Browse git
class WidgetBase(ComponentBase): """ Inherit from this base class to create widget components that can be rendered to html via the `presalytics.story.components.Renderer` class. This component also needs to build a method that allows the widget to be serialzed into a `presalytics.story.outline.Widget` object. Parameters ---------- widget: Widget A `presalytics.story.outline.Widget` object use for initialized the component class. Attributes ---------- outline_widget: Widget A `presalytics.story.outline.Widget` object """ outline_widget: typing.Optional['Widget'] __component_type__ = 'widget' def __init__(self, name, *args, **kwargs) -> None: super(WidgetBase, self).__init__(*args, **kwargs) self.name = name self.outline_widget = None def render(self, component, **kwargs): self.to_html(component, **kwargs) @abc.abstractmethod def to_html(self, data: typing.Dict = None, **kwargs) -> str: """ Returns valid html that renders the widget in a browser. Parameters ---------- data: dict The data parameter is a dictionary should contain the minimum amount of that is required to successfully render the object. As the widget is update, data control how the display of information changes. **kwargs: Optional keyword arguments can be used in subclass to modify the behavior of the `to_html` function. these keyword arguments should be invariant through successive updates to the chart. For example, keyword arguments could control the styling of the widget, which should not change as the data in the object (e.g., a chart) is updated. Keyword arguments are loaded via `additional_properties` parameter in in the `presalytics.story.outline.Widget` object. Returns ---------- A str of containing an html fragment that will be loaded into a template in successive operations """ raise NotImplementedError @abc.abstractmethod def serialize(self, **kwargs) -> 'Widget': """ Creates `presalytics.story.outline.Widget` object from instance data. This widget should have the correct `name`, `data` and `additional_properties` so the same widget can be reconstituted via the to_html method, given the same set of data. Typically, this method will call an update method that run a local script with updates this Widget's data Dictionary prior being loading into the Widget outline object for serialization. Parameters ---------- **kwargs: Optional keyword arguments can be used in subclass to modify the behavior of the to_html function. these keyword arguments should be be invariant through successive updates to the chart. Overrides for this widgets default additional_properties should be loaded via these keyword arguments. """ raise NotImplementedError @classmethod def deserialize(cls, component: 'Widget', **kwargs) -> 'WidgetBase': """ Creates an instance of the widget from the data object in the `presalytics.story.outline.Widget` object. This method exists to ensure widgets can be portable across environments. To clarify, widgets built on the client-side via the `__init__` method can be reconstructed server-side via the deserialize method. This allows decoupling of the widget generation/updating of data and the rendering of the widget in a UI. Renderers (e.g., `presalytics.story.revealer.Revealer` object) need not know about how the data get updated, but can update the graphic with data generated by the widget when the serialize method is called. Parameters ---------- widget: Widget A `presalytics.story.outline.Widget` object Returns ---------- `presalytics.story.components.WidgetBase` subclass instance """ raise NotImplementedError
Ancestors
- ComponentBase
- abc.ABC
Subclasses
Static methods
def deserialize(component, **kwargs)
-
Creates an instance of the widget from the data object in the
Widget
object. This method exists to ensure widgets can be portable across environments. To clarify, widgets built on the client-side via the__init__
method can be reconstructed server-side via the deserialize method. This allows decoupling of the widget generation/updating of data and the rendering of the widget in a UI. Renderers (e.g.,Revealer
object) need not know about how the data get updated, but can update the graphic with data generated by the widget when the serialize method is called.Parameters
widget
:Widget
- A
Widget
object
Returns
WidgetBase
subclass instanceExpand source code Browse git
@classmethod def deserialize(cls, component: 'Widget', **kwargs) -> 'WidgetBase': """ Creates an instance of the widget from the data object in the `presalytics.story.outline.Widget` object. This method exists to ensure widgets can be portable across environments. To clarify, widgets built on the client-side via the `__init__` method can be reconstructed server-side via the deserialize method. This allows decoupling of the widget generation/updating of data and the rendering of the widget in a UI. Renderers (e.g., `presalytics.story.revealer.Revealer` object) need not know about how the data get updated, but can update the graphic with data generated by the widget when the serialize method is called. Parameters ---------- widget: Widget A `presalytics.story.outline.Widget` object Returns ---------- `presalytics.story.components.WidgetBase` subclass instance """ raise NotImplementedError
Methods
def to_html(self, data=None, **kwargs)
-
Returns valid html that renders the widget in a browser.
Parameters
data
:dict
- The data parameter is a dictionary should contain the minimum amount of that is required to successfully render the object. As the widget is update, data control how the display of information changes.
**kwargs: Optional keyword arguments can be used in subclass to modify the behavior of the
to_html
function. these keyword arguments should be invariant through successive updates to the chart. For example, keyword arguments could control the styling of the widget, which should not change as the data in the object (e.g., a chart) is updated. Keyword arguments are loaded viaadditional_properties
parameter in in theWidget
object.Returns
A
str
ofcontaining
an
html
fragment
that
will
be
loaded
into
a
template
in
successive
operations
Expand source code Browse git
@abc.abstractmethod def to_html(self, data: typing.Dict = None, **kwargs) -> str: """ Returns valid html that renders the widget in a browser. Parameters ---------- data: dict The data parameter is a dictionary should contain the minimum amount of that is required to successfully render the object. As the widget is update, data control how the display of information changes. **kwargs: Optional keyword arguments can be used in subclass to modify the behavior of the `to_html` function. these keyword arguments should be invariant through successive updates to the chart. For example, keyword arguments could control the styling of the widget, which should not change as the data in the object (e.g., a chart) is updated. Keyword arguments are loaded via `additional_properties` parameter in in the `presalytics.story.outline.Widget` object. Returns ---------- A str of containing an html fragment that will be loaded into a template in successive operations """ raise NotImplementedError
def serialize(self, **kwargs)
-
Creates
Widget
object from instance data. This widget should have the correctname
,data
andadditional_properties
so the same widget can be reconstituted via the to_html method, given the same set of data.Typically, this method will call an update method that run a local script with updates this Widget's data Dictionary prior being loading into the Widget outline object for serialization.
Parameters
**kwargs: Optional keyword arguments can be used in subclass to modify the behavior of the to_html function. these keyword arguments should be be invariant through successive updates to the chart. Overrides for this widgets default additional_properties should be loaded via these keyword arguments.
Expand source code Browse git
@abc.abstractmethod def serialize(self, **kwargs) -> 'Widget': """ Creates `presalytics.story.outline.Widget` object from instance data. This widget should have the correct `name`, `data` and `additional_properties` so the same widget can be reconstituted via the to_html method, given the same set of data. Typically, this method will call an update method that run a local script with updates this Widget's data Dictionary prior being loading into the Widget outline object for serialization. Parameters ---------- **kwargs: Optional keyword arguments can be used in subclass to modify the behavior of the to_html function. these keyword arguments should be be invariant through successive updates to the chart. Overrides for this widgets default additional_properties should be loaded via these keyword arguments. """ raise NotImplementedError
Inherited members
class PageTemplateBase (page, **kwargs)
-
Inherit from this base class to render templates to html via the
Revealer
class.Parameters
page
:Page
- A presalytics.story.outline.Page object for instalizing the class
Attributes
outline_page
:Page
- The page data
widgets
:list
ofsubclasses
ofWidgetBase
- A list widget that will be loaded into templates and rendered via placeholders. These widgets must have a "to_html(self, data, **kwargs)" method.
Expand source code Browse git
class PageTemplateBase(ComponentBase): """ Inherit from this base class to render templates to html via the `presalytics.story.revealer.Revealer` class. Parameters ---------- page: Page A presalytics.story.outline.Page object for instalizing the class Attributes ---------- outline_page: presalytics.story.outline.Page The page data widgets: list of subclasses of presalytics.story.components.WidgetBase A list widget that will be loaded into templates and rendered via placeholders. These widgets must have a "to_html(self, data, **kwargs)" method. """ outline_page: 'Page' widgets: typing.List['WidgetBase'] plugins: typing.List[typing.Dict] __component_type__ = 'page' def __init__(self, page: 'Page', **kwargs) -> None: super(PageTemplateBase, self).__init__(**kwargs) self.outline_page = page self.widgets = self.get_page_widgets(self.outline_page) @abc.abstractmethod def render(self, **kwargs) -> str: """ Returns valid html that renders the template in a broswer with data loaded from widgets. """ raise NotImplementedError def load_widget(self, widget: 'Widget'): """ Converts a presalytics.story.outline.Widget object to a subclass of WidgetComponentBase via the `presalytics.COMPONENTS` registry. """ class_key = "widget." + widget.kind key = class_key + "." + widget.name try: if presalytics.COMPONENTS.get_instance(key): widget_instance = presalytics.COMPONENTS.get_instance(key) else: klass = presalytics.COMPONENTS.get(class_key) deserialize_method = getattr(klass, "deserialize", None) if callable(deserialize_method): widget_instance = deserialize_method(widget, client_info=self.client_info) else: message = "Widget component instance or class (kind) {0} unavailable in component registry".format(key) raise presalytics.lib.exceptions.MissingConfigException(message) except Exception as ex: logger.exception(ex) if not presalytics.CONFIG.get("DEBUG", False): widget_instance = presalytics.lib.exceptions.RenderExceptionHandler(ex) else: t, v, tb = sys.exc_info() six.reraise(t, v, tb) return widget_instance def get_page_widgets(self, page: 'Page'): """ Converts the widgets within a `presalytics.story.outline.Page` object to a list of widgets subclassed from `presalytics.story.components.WidgetBase` """ widget_instances = [] for widget_outline in page.widgets: next_widget = self.load_widget(widget_outline) widget_instances.append(next_widget) return widget_instances def serialize(self): return self.outline_page.dump() @classmethod def deseriailize(cls, component, **kwargs): return cls(page=component)
Ancestors
- ComponentBase
- abc.ABC
Subclasses
Static methods
def deseriailize(component, **kwargs)
-
Expand source code Browse git
@classmethod def deseriailize(cls, component, **kwargs): return cls(page=component)
Methods
def render(self, **kwargs)
-
Returns valid html that renders the template in a broswer with data loaded from widgets.
Expand source code Browse git
@abc.abstractmethod def render(self, **kwargs) -> str: """ Returns valid html that renders the template in a broswer with data loaded from widgets. """ raise NotImplementedError
def load_widget(self, widget)
-
Converts a presalytics.story.outline.Widget object to a subclass of WidgetComponentBase via the
COMPONENTS
registry.Expand source code Browse git
def load_widget(self, widget: 'Widget'): """ Converts a presalytics.story.outline.Widget object to a subclass of WidgetComponentBase via the `presalytics.COMPONENTS` registry. """ class_key = "widget." + widget.kind key = class_key + "." + widget.name try: if presalytics.COMPONENTS.get_instance(key): widget_instance = presalytics.COMPONENTS.get_instance(key) else: klass = presalytics.COMPONENTS.get(class_key) deserialize_method = getattr(klass, "deserialize", None) if callable(deserialize_method): widget_instance = deserialize_method(widget, client_info=self.client_info) else: message = "Widget component instance or class (kind) {0} unavailable in component registry".format(key) raise presalytics.lib.exceptions.MissingConfigException(message) except Exception as ex: logger.exception(ex) if not presalytics.CONFIG.get("DEBUG", False): widget_instance = presalytics.lib.exceptions.RenderExceptionHandler(ex) else: t, v, tb = sys.exc_info() six.reraise(t, v, tb) return widget_instance
def get_page_widgets(self, page)
-
Converts the widgets within a
Page
object to a list of widgets subclassed fromWidgetBase
Expand source code Browse git
def get_page_widgets(self, page: 'Page'): """ Converts the widgets within a `presalytics.story.outline.Page` object to a list of widgets subclassed from `presalytics.story.components.WidgetBase` """ widget_instances = [] for widget_outline in page.widgets: next_widget = self.load_widget(widget_outline) widget_instances.append(next_widget) return widget_instances
Inherited members
class ScssPlugin (**kwargs)
-
Compiles scss from files and a
dict
of variables and loads them into a script tag*Note: Any
<script>
tags nested into the resultant style tag will be removed at render-time by aRenderer
. These script tags will not make it to the browser.Configuration Values
filenames
:list
ofstr
- A list of scss filenames that will get compiled to css if all files are present
rendered_css
:str
, optional- If the files are not present, this string will be placed in the resultant style tag
variables
:dict
, optional- Scss variables to include. Dictionary keys should be prefixed with a
$
to indicate that they are scss variable names.
Expand source code Browse git
class ScssPlugin(presalytics.lib.plugins.base.StylePlugin): """ Compiles scss from files and a `dict` of variables and loads them into a script tag *Note: Any `<script>` tags nested into the resultant style tag will be removed at render-time by a `presalytics.story.components.Renderer`. These script tags will not make it to the browser. Configuration Values ---------- filenames : list of str A list of scss filenames that will get compiled to css if all files are present rendered_css : str, optional If the files are not present, this string will be placed in the resultant style tag variables : dict, optional Scss variables to include. Dictionary keys should be prefixed with a `$` to indicate that they are scss variable names. """ __plugin_name__ = "scss_files" def __init__(self, **kwargs): super(ScssPlugin, self).__init__(**kwargs) def _files_are_local(self, filenames): for fname in filenames: if not os.path.exists(fname): return False return True def _create_variables_string(self, variables): _vars = "" for key, val in variables.items(): _vars = _vars + key + ": " + val + "; " return _vars def _load_files_to_string(self, filenames): scss_string = "" for fname in filenames: with open(fname, 'r') as f: addl_scss = f.read() scss_string = scss_string + addl_scss return scss_string def make_css(self, filenames: typing.List[str], variables: typing.Dict[str, str]): """ Compiles the scss in filenames to an html fragment using variables Returns ---------- A `<style>` tag html fragment in a string """ _scss = self._create_variables_string(variables) + self._load_files_to_string(filenames) return sass.compile(string=_scss) def to_style(self, config, **kwargs): """ Renders a config containing `Configuration Values` to a style tag """ filenames = config.get("filenames", None) if not filenames: raise presalytics.lib.exceptions.MissingConfigException("Filenames are required in the config.") variables = config.get("variables", {}) if self._files_are_local(filenames): rendered_css = self.make_css(filenames, variables) elif config.get("rendered_css", None): rendered_css = config.get("rendered_css") else: raise presalytics.lib.exceptions.MissingConfigException("A file referenced by this plugin could not be found.") return "<style>{0}</style>".format(rendered_css) @classmethod def configure(cls, filenames: typing.List[str], variables: typing.Dict = {}, old_css: str = None) -> typing.Dict: """ Call this method when serializing a subclass of `presalytics.story.components.ComponentBase` to generate a configuration for the plugin with a valid `rendered_css` entry """ inst = cls() if not filenames: raise presalytics.lib.exceptions.MissingConfigException("Filenames are required in the config.") if inst._files_are_local(filenames): rendered_css = inst.make_css(filenames, variables) elif old_css: rendered_css = old_css else: raise presalytics.lib.exceptions.MissingConfigException("A file referenced by this plugin could not be found.") ret = { "name": inst.__plugin_name__, "kind": inst.__plugin_kind__, "config": { "filenames": filenames, "variables": variables, "rendered_css": rendered_css } } return ret
Ancestors
- StylePlugin
- PluginBase
- abc.ABC
Static methods
def configure(filenames, variables={}, old_css=None)
-
Call this method when serializing a subclass of
ComponentBase
to generate a configuration for the plugin with a validrendered_css
entryExpand source code Browse git
@classmethod def configure(cls, filenames: typing.List[str], variables: typing.Dict = {}, old_css: str = None) -> typing.Dict: """ Call this method when serializing a subclass of `presalytics.story.components.ComponentBase` to generate a configuration for the plugin with a valid `rendered_css` entry """ inst = cls() if not filenames: raise presalytics.lib.exceptions.MissingConfigException("Filenames are required in the config.") if inst._files_are_local(filenames): rendered_css = inst.make_css(filenames, variables) elif old_css: rendered_css = old_css else: raise presalytics.lib.exceptions.MissingConfigException("A file referenced by this plugin could not be found.") ret = { "name": inst.__plugin_name__, "kind": inst.__plugin_kind__, "config": { "filenames": filenames, "variables": variables, "rendered_css": rendered_css } } return ret
Methods
def make_css(self, filenames, variables)
-
Compiles the scss in filenames to an html fragment using variables
Returns
A
<style>
tag html fragment in a stringExpand source code Browse git
def make_css(self, filenames: typing.List[str], variables: typing.Dict[str, str]): """ Compiles the scss in filenames to an html fragment using variables Returns ---------- A `<style>` tag html fragment in a string """ _scss = self._create_variables_string(variables) + self._load_files_to_string(filenames) return sass.compile(string=_scss)
def to_style(self, config, **kwargs)
-
Renders a config containing
Configuration Values
to a style tagExpand source code Browse git
def to_style(self, config, **kwargs): """ Renders a config containing `Configuration Values` to a style tag """ filenames = config.get("filenames", None) if not filenames: raise presalytics.lib.exceptions.MissingConfigException("Filenames are required in the config.") variables = config.get("variables", {}) if self._files_are_local(filenames): rendered_css = self.make_css(filenames, variables) elif config.get("rendered_css", None): rendered_css = config.get("rendered_css") else: raise presalytics.lib.exceptions.MissingConfigException("A file referenced by this plugin could not be found.") return "<style>{0}</style>".format(rendered_css)
Inherited members