Module presalytics.lib.widgets.ooxml
Expand source code Browse git
import typing
import time
import abc
import urllib.parse
import requests
import os
import datetime
import dateutil.parser
import lxml
import lxml.etree
import posixpath
import pandas
import collections
import presalytics
import presalytics.client.api
import presalytics.story.components
import presalytics.client
import presalytics.lib.exceptions
import presalytics.story.outline
import presalytics.lib.util
import presalytics.lib.plugins.ooxml
import presalytics.client.presalytics_ooxml_automation.models.chart_chart_data_dto
import presalytics.client.presalytics_ooxml_automation.models.table_table_data_dto
if typing.TYPE_CHECKING:
from presalytics.story.outline import StoryOutline, Page, Widget
from presalytics.client.presalytics_story import Story as ApiStory
from presalytics.client.presalytics_ooxml_automation.models.chart_chart_data_dto import ChartChartDataDTO
from presalytics.client.presalytics_ooxml_automation.models.table_table_data_dto import TableTableDataDTO
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)
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
)
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
class UpdaterWidgetBase(OoxmlWidgetBase):
"""
Abstract class for create simple interfaces to update widgets from a simple data table.
This class simplifies updates to Ooxml Automation service endpoints, allowing updates to
ooxml object data and its underlying xml via simple data transfer objects definted in the
Presalytics Ooxml Automation server.
Inheriting classes must override the `_get_dto_class`, `_get_endpoint_path`, and `_get_dto_table_name`
methods
Parameters
----------
name : str
The widget name. If not provided, will be 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_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
dto: object, 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.
"""
def __init__(self,
name,
story_id: str,
object_id: str,
endpoint_map: OoxmlEndpointMap,
dto=None,
data_table=None,
**kwargs):
super(UpdaterWidgetBase, self).__init__(name, story_id, object_id, endpoint_map, **kwargs)
self.dto = dto
self.data_table = data_table
self.object_id = object_id
@abc.abstractmethod
def _get_dto_class(self) -> typing.Type:
"""
Returns the Class of instances that will used for dtos
"""
return NotImplemented
@abc.abstractmethod
def _get_endpoint_path(self) -> str:
"""
Returns the relative path to the the endpoint used , starting from the `root_url` of the
`presalytics.lib.widgets.ooxml.OoxmlEndpointMap` object.
"""
return NotImplemented
@abc.abstractmethod
def _get_dto_table_name(self) -> str:
"""
Returns the attribute name in the object returned by `_get_dto_class()` that the `data_table` parameter
should be mapped to.
"""
return NotImplemented
def build_endpoint(self) -> str:
"""
Returns the endpoints used for Api calls
"""
return posixpath.join(self.endpoint_map.root_url, self._get_endpoint_path(), self.object_ooxml_id)
def get_dto(self):
"""
Returns an instance of the dto object from the OoxmlAutomation Service API
"""
client = self.get_client()
headers = client.get_auth_header()
headers.update(client.get_request_id_header())
resp = requests.get(self.build_endpoint(), headers=headers)
if resp.status_code > 299:
raise presalytics.lib.exceptions.ApiError(message=resp.text)
dto = client.ooxml_automation.api_client._ApiClient__deserialize(resp.json(), self._get_dto_class())
self.dto = dto
return dto
def _put_dto(self, dto):
"""
Updates the Endpoint in the Ooxml Automation Service from a dto object
"""
client = self.get_client()
headers = client.get_auth_header()
headers.update(client.get_request_id_header())
data = client.ooxml_automation.api_client.sanitize_for_serialization(dto)
resp = requests.put(self.build_endpoint(), json=data, headers=headers)
if resp.status_code > 299:
raise presalytics.lib.exceptions.ApiError(message=resp.text)
else:
self.dto = dto
def update_from_dto(self, dto):
"""
Updates the Endpoint in the Ooxml Automation Service from a dto object, and updates
the `svg_html` attribute
"""
self._put_dto(dto)
self.svg_html = self.create_container()
def update_from_data_table(self, data_table):
"""
Updates the Endpoint in the Ooxml Automation Service from a the `data_table`, and updates
the `svg_html` attribute
"""
dto = self.get_dto()
setattr(dto, self._get_dto_table_name(), data_table)
self.put_dto(dto)
def serialize(self):
data = {
"story_id": self.story_id,
"object_id": self.object_ooxml_id,
"dto": self.dto.to_dict(),
"data_table": self.data_table
}
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"],
component.data.get("dto", None),
component.data.get("data_table", None),
**kwargs
)
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)
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"
Classes
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 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 UpdaterWidgetBase (name, story_id, object_id, endpoint_map, dto=None, data_table=None, **kwargs)
-
Abstract class for create simple interfaces to update widgets from a simple data table.
This class simplifies updates to Ooxml Automation service endpoints, allowing updates to ooxml object data and its underlying xml via simple data transfer objects definted in the Presalytics Ooxml Automation server.
Inheriting classes must override the
_get_dto_class
,_get_endpoint_path
, and_get_dto_table_name
methodsParameters
name
:str
- The widget name.
If not provided, will be 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_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
dto
:object
, 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 UpdaterWidgetBase(OoxmlWidgetBase): """ Abstract class for create simple interfaces to update widgets from a simple data table. This class simplifies updates to Ooxml Automation service endpoints, allowing updates to ooxml object data and its underlying xml via simple data transfer objects definted in the Presalytics Ooxml Automation server. Inheriting classes must override the `_get_dto_class`, `_get_endpoint_path`, and `_get_dto_table_name` methods Parameters ---------- name : str The widget name. If not provided, will be 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_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 dto: object, 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. """ def __init__(self, name, story_id: str, object_id: str, endpoint_map: OoxmlEndpointMap, dto=None, data_table=None, **kwargs): super(UpdaterWidgetBase, self).__init__(name, story_id, object_id, endpoint_map, **kwargs) self.dto = dto self.data_table = data_table self.object_id = object_id @abc.abstractmethod def _get_dto_class(self) -> typing.Type: """ Returns the Class of instances that will used for dtos """ return NotImplemented @abc.abstractmethod def _get_endpoint_path(self) -> str: """ Returns the relative path to the the endpoint used , starting from the `root_url` of the `presalytics.lib.widgets.ooxml.OoxmlEndpointMap` object. """ return NotImplemented @abc.abstractmethod def _get_dto_table_name(self) -> str: """ Returns the attribute name in the object returned by `_get_dto_class()` that the `data_table` parameter should be mapped to. """ return NotImplemented def build_endpoint(self) -> str: """ Returns the endpoints used for Api calls """ return posixpath.join(self.endpoint_map.root_url, self._get_endpoint_path(), self.object_ooxml_id) def get_dto(self): """ Returns an instance of the dto object from the OoxmlAutomation Service API """ client = self.get_client() headers = client.get_auth_header() headers.update(client.get_request_id_header()) resp = requests.get(self.build_endpoint(), headers=headers) if resp.status_code > 299: raise presalytics.lib.exceptions.ApiError(message=resp.text) dto = client.ooxml_automation.api_client._ApiClient__deserialize(resp.json(), self._get_dto_class()) self.dto = dto return dto def _put_dto(self, dto): """ Updates the Endpoint in the Ooxml Automation Service from a dto object """ client = self.get_client() headers = client.get_auth_header() headers.update(client.get_request_id_header()) data = client.ooxml_automation.api_client.sanitize_for_serialization(dto) resp = requests.put(self.build_endpoint(), json=data, headers=headers) if resp.status_code > 299: raise presalytics.lib.exceptions.ApiError(message=resp.text) else: self.dto = dto def update_from_dto(self, dto): """ Updates the Endpoint in the Ooxml Automation Service from a dto object, and updates the `svg_html` attribute """ self._put_dto(dto) self.svg_html = self.create_container() def update_from_data_table(self, data_table): """ Updates the Endpoint in the Ooxml Automation Service from a the `data_table`, and updates the `svg_html` attribute """ dto = self.get_dto() setattr(dto, self._get_dto_table_name(), data_table) self.put_dto(dto) def serialize(self): data = { "story_id": self.story_id, "object_id": self.object_ooxml_id, "dto": self.dto.to_dict(), "data_table": self.data_table } 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"], component.data.get("dto", None), component.data.get("data_table", None), **kwargs )
Ancestors
- OoxmlWidgetBase
- WidgetBase
- ComponentBase
- abc.ABC
Subclasses
Methods
def build_endpoint(self)
-
Returns the endpoints used for Api calls
Expand source code Browse git
def build_endpoint(self) -> str: """ Returns the endpoints used for Api calls """ return posixpath.join(self.endpoint_map.root_url, self._get_endpoint_path(), self.object_ooxml_id)
def get_dto(self)
-
Returns an instance of the dto object from the OoxmlAutomation Service API
Expand source code Browse git
def get_dto(self): """ Returns an instance of the dto object from the OoxmlAutomation Service API """ client = self.get_client() headers = client.get_auth_header() headers.update(client.get_request_id_header()) resp = requests.get(self.build_endpoint(), headers=headers) if resp.status_code > 299: raise presalytics.lib.exceptions.ApiError(message=resp.text) dto = client.ooxml_automation.api_client._ApiClient__deserialize(resp.json(), self._get_dto_class()) self.dto = dto return dto
def update_from_dto(self, dto)
-
Updates the Endpoint in the Ooxml Automation Service from a dto object, and updates the
svg_html
attributeExpand source code Browse git
def update_from_dto(self, dto): """ Updates the Endpoint in the Ooxml Automation Service from a dto object, and updates the `svg_html` attribute """ self._put_dto(dto) self.svg_html = self.create_container()
def update_from_data_table(self, data_table)
-
Updates the Endpoint in the Ooxml Automation Service from a the
data_table
, and updates thesvg_html
attributeExpand source code Browse git
def update_from_data_table(self, data_table): """ Updates the Endpoint in the Ooxml Automation Service from a the `data_table`, and updates the `svg_html` attribute """ dto = self.get_dto() setattr(dto, self._get_dto_table_name(), data_table) self.put_dto(dto)
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