Module presalytics.lib.tools.workflows
Expand source code Browse git
import typing
import os
import sys
import json
import jsonpatch
import jsonpointer
import logging
import presalytics.client.api
import presalytics.lib.tools.story_tools
import presalytics.story.outline
import presalytics.story.components
if typing.TYPE_CHECKING:
from presalytics.story.components import ComponentBase, WidgetBase
from presalytics.story.outline import StoryOutline
logger = logging.getLogger(__name__)
def update_components(filename=None):
autodiscover_paths = presalytics.COMPONENTS.autodiscover_paths
dirname = os.path.dirname(filename)
if os.path.exists(dirname) and dirname not in autodiscover_paths:
autodiscover_paths.append(dirname)
cwd = os.getcwd()
abs_filedir = os.path.join(cwd, dirname)
if os.path.exists(abs_filedir) and abs_filedir not in autodiscover_paths:
autodiscover_paths.append(abs_filedir)
if len(autodiscover_paths) > len(presalytics.COMPONENTS.autodiscover_paths):
presalytics.COMPONENTS = presalytics.story.components.ComponentRegistry(autodiscover_paths=autodiscover_paths,
reserved_names=presalytics.CONFIG.get("RESERVED_NAMES", []))
def get_component(name, filename=None):
inst: 'ComponentBase'
if filename:
update_components(filename=filename)
component_list = presalytics.COMPONENTS.find_instance(name)
if len(component_list) == 0:
message = "Could not find an instance with name '{0}' in the COMPONENTS registry".format(name)
presalytics.COMPONENTS.raise_error(message)
elif len(component_list) > 1:
components_str = ", ".join(component_list)
message = "Multiple matches for name in COMPONENTS registry. Choose from: " + components_str
presalytics.COMPONENTS.raise_error(message)
elif len(component_list) == 1:
return presalytics.COMPONENTS.get_instance(component_list[0])
else:
message = "Error searching the COMPONENTS registry"
presalytics.COMPONENTS.raise_error(message)
def add_widget_instance(widget_name, outline, position=None, page_number=0, filename=None):
inst: 'WidgetBase'
outline: 'StoryOutline'
inst = get_component(widget_name, filename=filename)
widget = inst.serialize()
if not page_number:
page_number = 0
if page_number > len(outline.pages):
message = "The story has fewer than {} pages".format(page_number)
raise presalytics.lib.exceptions.InvalidArgumentException(message=message)
if not position:
position = len(outline.pages[page_number].widgets)
outline.pages[page_number].widgets.insert(position, widget)
return outline
def remove_widget_by_name(widget_name, outline, page_number=None, filename=None):
inst: 'WidgetBase'
outline: 'StoryOutline'
if page_number:
start = page_number
stop = page_number
else:
start = 0
stop = len(outline.pages)
for p in range(start, stop):
page = outline.pages[p]
for w in range(0, len(page.widgets)):
if page.widgets[w].name == widget_name:
page.widgets.pop(w)
return outline
def create_from_instance(name, widget=False, page=False, filename=None) -> 'StoryOutline': #type: ignore
"""
Creates a Story Outline from an instance name in `presalytics.COMPONENTS`
"""
inst: 'ComponentBase'
inst = get_component(name)
if page:
outline = presalytics.lib.tools.component_tools.create_outline_from_page(inst) #type: ignore
elif widget:
outline = presalytics.lib.tools.component_tools.create_outline_from_widget(inst) #type: ignore
else:
raise presalytics.lib.exceptions.InvalidArgumentException("Either the 'widget' or 'page' paramters must be true.")
return outline
def push_outline(outline, username=None, password=None) -> 'StoryOutline':
"""
Updates an outline in the Presalytics API Story service
"""
client = presalytics.client.api.Client(username=username, password=password)
if outline.story_id == "empty" or not outline.story_id:
story = client.story.story_post({"outline": outline.dump()})
else:
old_story = client.story.story_id_get(outline.story_id)
old_story.outline = outline.dump()
story = client.story.story_id_outline_post(old_story.id, outline.dump())
synced_outline_str = client.await_outline(story.id)
return presalytics.story.outline.StoryOutline.load(synced_outline_str)
def pull_outline(story_id, username=None, password=None) -> 'StoryOutline':
"""
Retrives an outline from the Presalytics API Story service
"""
client = presalytics.client.api.Client(username=username, password=password)
if story_id == "empty" or not story_id:
message = "The story_id must not be empty"
raise presalytics.lib.exceptions.InvalidArgumentException(message)
story = client.story.story_id_get(story_id)
return presalytics.story.outline.StoryOutline.load(story.outline)
def delete_all_stories(username=None, password=None):
"""
WARNING!!!
Delete all stories owned by this user
BE CAREFUL!!!
"""
client = presalytics.client.api.Client(username=username, password=password)
stories = client.story.story_get()
ids = [s.id for s in stories]
for id in ids:
client.story.story_id_delete(id)
def delete_by_id(story_id, username=None, password=None):
"""
Delete stories by Story Id
"""
client = presalytics.client.api.Client(username=username, password=password)
client.story.story_id_delete(story_id)
def share_story(story_id,
emails=None,
user_ids=None,
username=None,
password=None,
collaborator_type="viewer"):
"""
Share stories with other users. Either by email or Presalytics API user Id.
"""
client = presalytics.client.api.Client(username=username, password=password)
if emails:
email_list = emails.split(",")
for e in email_list:
body = {
"user_email": e,
"collaborator_type": collaborator_type,
"user_id": ""
}
client.story.story_id_collaborators_post(story_id, body)
if user_ids:
user_id_list = user_ids.split(",")
for _id in user_id_list:
body = {
"user_email": "",
"collaborator_type": collaborator_type,
"user_id": _id
}
client.story.story_id_collaborators_post(story_id, body)
def get_story(story_id):
"""
returns Story metadata from the story API
"""
client = presalytics.client.api.Client()
return client.story.story_id_get(story_id, include_relationships=True)
def create_config_file(username, password=None, set_dict={}, overwrite=False):
config_file = os.path.join(os.getcwd(), "config.py")
if os.path.exists(config_file):
if overwrite:
os.remove(config_file)
else:
logger.error("Configuration file exists. Use option [--overwrite] to overwrite.")
raise ValueError
dump = {"USERNAME": username}
if password:
dump.update({"PASSWORD": password})
if len(set_dict.keys()) > 0:
dump.update(set_dict)
content = "PRESALYTICS = " + repr(dump)
with open(config_file, 'w') as f:
f.write(content)
def create_cron_target():
if sys.platform.startswith("linux"):
cron_command = "* * * * * cd {0} && {1} -m presalytics push".format(os.getcwd(), sys.executable)
print("Place the following line in your cron file:")
print(cron_command)
elif sys.platform.startswith("win"):
command = os.environ["VIRTUAL_ENV"] + "/Scripts/activate.bat && python -m presalytics push"
filename = os.path.join(os.getcwd(), "story-push.bat")
with open(filename, 'w') as f:
f.write(command)
print("A file named 'story-push.bat was just placed in the current working directory. Add this file in the 'Windows Task Scheduler'.")
else:
logger.error("Unknown operating system.")
raise ValueError
def apply_json_patch(outline, patch):
"""
Allows users to apply json patches to thier Story Outlines.
See https://python-json-patch.readthedocs.io/en/latest/index.html for more info.
Parameters:
----------
outline: presaltyics.story.outline.StoryOutline
A Story Outline object
patch: str or dict
Either an RFC 6901 compliant string or a dictionary with op, path, and value keys. The
dictionary is for ease of use and will get parsed into a compliant string.
"""
outline: 'StoryOutline'
if isinstance(patch, dict):
_json = json.dumps(patch)
patch = '[{}]'.format(_json)
outline_dict = outline.to_dict()
try:
new_dict = jsonpatch.apply_patch(outline_dict, patch)
except (jsonpatch.JsonPatchException, jsonpointer.JsonPointerException) as ex:
message = "Unable to apply Json patch: {}".format(ex.args[0])
raise presalytics.lib.exceptions.InvalidArgumentException(message=message)
new_outline = presalytics.story.outline.StoryOutline.deserialize(new_dict)
return new_outline
def update_outline(outline, filename=None, message=None):
"""
Updates the the outline from active instances in the workspace
"""
if not message:
message = "Automated update via presalytics.lib.tools.workflows.update_outline()"
update_components(filename=filename)
for p in range(0, len(outline.pages)):
page = outline.pages[p]
page_key = "page.{}.{}".format(page.kind, page.name)
page_inst = presalytics.COMPONENTS.get_instance(page_key)
if page_inst:
outline.pages[p] = page_inst.serialize()
for w in range(0, len(page.widgets)):
widget = outline.pages[p].widgets[w]
widget_key = "widget.{}.{}".format(widget.kind, widget.name)
widget_inst = presalytics.COMPONENTS.get_instance(widget_key)
if widget_inst:
outline.pages[p].widgets[w] = widget_inst.serialize()
for t in range(0, len(outline.themes)):
theme = outline.themes[t]
theme_key = "theme.{}.{}".format(theme.kind, theme.name)
theme_inst = presalytics.COMPONENTS.get_instance(theme_key)
if theme_inst:
outline.themes[t] = theme_inst.serialize()
outline.info.revision_notes = message
return outline
Functions
def update_components(filename=None)
-
Expand source code Browse git
def update_components(filename=None): autodiscover_paths = presalytics.COMPONENTS.autodiscover_paths dirname = os.path.dirname(filename) if os.path.exists(dirname) and dirname not in autodiscover_paths: autodiscover_paths.append(dirname) cwd = os.getcwd() abs_filedir = os.path.join(cwd, dirname) if os.path.exists(abs_filedir) and abs_filedir not in autodiscover_paths: autodiscover_paths.append(abs_filedir) if len(autodiscover_paths) > len(presalytics.COMPONENTS.autodiscover_paths): presalytics.COMPONENTS = presalytics.story.components.ComponentRegistry(autodiscover_paths=autodiscover_paths, reserved_names=presalytics.CONFIG.get("RESERVED_NAMES", []))
def get_component(name, filename=None)
-
Expand source code Browse git
def get_component(name, filename=None): inst: 'ComponentBase' if filename: update_components(filename=filename) component_list = presalytics.COMPONENTS.find_instance(name) if len(component_list) == 0: message = "Could not find an instance with name '{0}' in the COMPONENTS registry".format(name) presalytics.COMPONENTS.raise_error(message) elif len(component_list) > 1: components_str = ", ".join(component_list) message = "Multiple matches for name in COMPONENTS registry. Choose from: " + components_str presalytics.COMPONENTS.raise_error(message) elif len(component_list) == 1: return presalytics.COMPONENTS.get_instance(component_list[0]) else: message = "Error searching the COMPONENTS registry" presalytics.COMPONENTS.raise_error(message)
def add_widget_instance(widget_name, outline, position=None, page_number=0, filename=None)
-
Expand source code Browse git
def add_widget_instance(widget_name, outline, position=None, page_number=0, filename=None): inst: 'WidgetBase' outline: 'StoryOutline' inst = get_component(widget_name, filename=filename) widget = inst.serialize() if not page_number: page_number = 0 if page_number > len(outline.pages): message = "The story has fewer than {} pages".format(page_number) raise presalytics.lib.exceptions.InvalidArgumentException(message=message) if not position: position = len(outline.pages[page_number].widgets) outline.pages[page_number].widgets.insert(position, widget) return outline
def remove_widget_by_name(widget_name, outline, page_number=None, filename=None)
-
Expand source code Browse git
def remove_widget_by_name(widget_name, outline, page_number=None, filename=None): inst: 'WidgetBase' outline: 'StoryOutline' if page_number: start = page_number stop = page_number else: start = 0 stop = len(outline.pages) for p in range(start, stop): page = outline.pages[p] for w in range(0, len(page.widgets)): if page.widgets[w].name == widget_name: page.widgets.pop(w) return outline
def create_from_instance(name, widget=False, page=False, filename=None)
-
Creates a Story Outline from an instance name in
COMPONENTS
Expand source code Browse git
def create_from_instance(name, widget=False, page=False, filename=None) -> 'StoryOutline': #type: ignore """ Creates a Story Outline from an instance name in `presalytics.COMPONENTS` """ inst: 'ComponentBase' inst = get_component(name) if page: outline = presalytics.lib.tools.component_tools.create_outline_from_page(inst) #type: ignore elif widget: outline = presalytics.lib.tools.component_tools.create_outline_from_widget(inst) #type: ignore else: raise presalytics.lib.exceptions.InvalidArgumentException("Either the 'widget' or 'page' paramters must be true.") return outline
def push_outline(outline, username=None, password=None)
-
Updates an outline in the Presalytics API Story service
Expand source code Browse git
def push_outline(outline, username=None, password=None) -> 'StoryOutline': """ Updates an outline in the Presalytics API Story service """ client = presalytics.client.api.Client(username=username, password=password) if outline.story_id == "empty" or not outline.story_id: story = client.story.story_post({"outline": outline.dump()}) else: old_story = client.story.story_id_get(outline.story_id) old_story.outline = outline.dump() story = client.story.story_id_outline_post(old_story.id, outline.dump()) synced_outline_str = client.await_outline(story.id) return presalytics.story.outline.StoryOutline.load(synced_outline_str)
def pull_outline(story_id, username=None, password=None)
-
Retrives an outline from the Presalytics API Story service
Expand source code Browse git
def pull_outline(story_id, username=None, password=None) -> 'StoryOutline': """ Retrives an outline from the Presalytics API Story service """ client = presalytics.client.api.Client(username=username, password=password) if story_id == "empty" or not story_id: message = "The story_id must not be empty" raise presalytics.lib.exceptions.InvalidArgumentException(message) story = client.story.story_id_get(story_id) return presalytics.story.outline.StoryOutline.load(story.outline)
def delete_all_stories(username=None, password=None)
-
WARNING!!!
Delete all stories owned by this user
BE CAREFUL!!!
Expand source code Browse git
def delete_all_stories(username=None, password=None): """ WARNING!!! Delete all stories owned by this user BE CAREFUL!!! """ client = presalytics.client.api.Client(username=username, password=password) stories = client.story.story_get() ids = [s.id for s in stories] for id in ids: client.story.story_id_delete(id)
def delete_by_id(story_id, username=None, password=None)
-
Delete stories by Story Id
Expand source code Browse git
def delete_by_id(story_id, username=None, password=None): """ Delete stories by Story Id """ client = presalytics.client.api.Client(username=username, password=password) client.story.story_id_delete(story_id)
-
Share stories with other users. Either by email or Presalytics API user Id.
Expand source code Browse git
def share_story(story_id, emails=None, user_ids=None, username=None, password=None, collaborator_type="viewer"): """ Share stories with other users. Either by email or Presalytics API user Id. """ client = presalytics.client.api.Client(username=username, password=password) if emails: email_list = emails.split(",") for e in email_list: body = { "user_email": e, "collaborator_type": collaborator_type, "user_id": "" } client.story.story_id_collaborators_post(story_id, body) if user_ids: user_id_list = user_ids.split(",") for _id in user_id_list: body = { "user_email": "", "collaborator_type": collaborator_type, "user_id": _id } client.story.story_id_collaborators_post(story_id, body)
def get_story(story_id)
-
returns Story metadata from the story API
Expand source code Browse git
def get_story(story_id): """ returns Story metadata from the story API """ client = presalytics.client.api.Client() return client.story.story_id_get(story_id, include_relationships=True)
def create_config_file(username, password=None, set_dict={}, overwrite=False)
-
Expand source code Browse git
def create_config_file(username, password=None, set_dict={}, overwrite=False): config_file = os.path.join(os.getcwd(), "config.py") if os.path.exists(config_file): if overwrite: os.remove(config_file) else: logger.error("Configuration file exists. Use option [--overwrite] to overwrite.") raise ValueError dump = {"USERNAME": username} if password: dump.update({"PASSWORD": password}) if len(set_dict.keys()) > 0: dump.update(set_dict) content = "PRESALYTICS = " + repr(dump) with open(config_file, 'w') as f: f.write(content)
def create_cron_target()
-
Expand source code Browse git
def create_cron_target(): if sys.platform.startswith("linux"): cron_command = "* * * * * cd {0} && {1} -m presalytics push".format(os.getcwd(), sys.executable) print("Place the following line in your cron file:") print(cron_command) elif sys.platform.startswith("win"): command = os.environ["VIRTUAL_ENV"] + "/Scripts/activate.bat && python -m presalytics push" filename = os.path.join(os.getcwd(), "story-push.bat") with open(filename, 'w') as f: f.write(command) print("A file named 'story-push.bat was just placed in the current working directory. Add this file in the 'Windows Task Scheduler'.") else: logger.error("Unknown operating system.") raise ValueError
def apply_json_patch(outline, patch)
-
Allows users to apply json patches to thier Story Outlines.
See https://python-json-patch.readthedocs.io/en/latest/index.html for more info.
Parameters:
outline: presaltyics.story.outline.StoryOutline A Story Outline object
patch: str or dict Either an RFC 6901 compliant string or a dictionary with op, path, and value keys. The dictionary is for ease of use and will get parsed into a compliant string.
Expand source code Browse git
def apply_json_patch(outline, patch): """ Allows users to apply json patches to thier Story Outlines. See https://python-json-patch.readthedocs.io/en/latest/index.html for more info. Parameters: ---------- outline: presaltyics.story.outline.StoryOutline A Story Outline object patch: str or dict Either an RFC 6901 compliant string or a dictionary with op, path, and value keys. The dictionary is for ease of use and will get parsed into a compliant string. """ outline: 'StoryOutline' if isinstance(patch, dict): _json = json.dumps(patch) patch = '[{}]'.format(_json) outline_dict = outline.to_dict() try: new_dict = jsonpatch.apply_patch(outline_dict, patch) except (jsonpatch.JsonPatchException, jsonpointer.JsonPointerException) as ex: message = "Unable to apply Json patch: {}".format(ex.args[0]) raise presalytics.lib.exceptions.InvalidArgumentException(message=message) new_outline = presalytics.story.outline.StoryOutline.deserialize(new_dict) return new_outline
def update_outline(outline, filename=None, message=None)
-
Updates the the outline from active instances in the workspace
Expand source code Browse git
def update_outline(outline, filename=None, message=None): """ Updates the the outline from active instances in the workspace """ if not message: message = "Automated update via presalytics.lib.tools.workflows.update_outline()" update_components(filename=filename) for p in range(0, len(outline.pages)): page = outline.pages[p] page_key = "page.{}.{}".format(page.kind, page.name) page_inst = presalytics.COMPONENTS.get_instance(page_key) if page_inst: outline.pages[p] = page_inst.serialize() for w in range(0, len(page.widgets)): widget = outline.pages[p].widgets[w] widget_key = "widget.{}.{}".format(widget.kind, widget.name) widget_inst = presalytics.COMPONENTS.get_instance(widget_key) if widget_inst: outline.pages[p].widgets[w] = widget_inst.serialize() for t in range(0, len(outline.themes)): theme = outline.themes[t] theme_key = "theme.{}.{}".format(theme.kind, theme.name) theme_inst = presalytics.COMPONENTS.get_instance(theme_key) if theme_inst: outline.themes[t] = theme_inst.serialize() outline.info.revision_notes = message return outline