8000 Add subscriptions by williamlang · Pull Request #240 · tableau/server-client-python · GitHub
[go: up one dir, main page]

Skip to content

Add subscriptions #240

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Oct 9, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions tableauserverclient/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
from .models import ConnectionCredentials, ConnectionItem, DatasourceItem,\
GroupItem, PaginationItem, ProjectItem, ScheduleItem, \
SiteItem, TableauAuth, UserItem, ViewItem, WorkbookItem, UnpopulatedPropertyError, \
HourlyInterval, DailyInterval, WeeklyInterval, MonthlyInterval, IntervalItem, TaskItem
HourlyInterval, DailyInterval, WeeklyInterval, MonthlyInterval, IntervalItem, TaskItem, \
SubscriptionItem
from .server import RequestOptions, ImageRequestOptions, PDFRequestOptions, Filter, Sort, \
Server, ServerResponseError, MissingRequiredFieldError, NotSignedInError, Pager

from ._version import get_versions
__version__ = get_versions()['version']
__VERSION__ = __version__
Expand Down
1 change: 1 addition & 0 deletions tableauserverclient/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@
from .user_item import UserItem
from .view_item import ViewItem
from .workbook_item import WorkbookItem
from .subscription_item import SubscriptionItem
57 changes: 57 additions & 0 deletions tableauserverclient/models/subscription_item.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import xml.etree.ElementTree as ET
from .exceptions import UnpopulatedPropertyError
from .target import Target


class SubscriptionItem(object):

def __init__(self, subject, schedule_id, user_id, target):
self.id = None
self.subject = subject
self.schedule_id = schedule_id
self.user_id = user_id
self.target = target

def __repr__(self):
if self.id is not None:
return "<Subscription#{id} subject({subject}) schedule_id({schedule_id}) user_id({user_id}) \
target({target})".format(**self.__dict__)
else:
return "<Subscription subject({subject}) schedule_id({schedule_id}) user_id({user_id}) \
target({target})".format(**self.__dict__)

def _set_id(self, id_):
self.id = id_

@classmethod
def from_response(cls, xml, ns):
parsed_response = ET.fromstring(xml)
all_subscriptions_xml = parsed_response.findall(
'.//t:subscription', namespaces=ns)

all_subscriptions = [SubscriptionItem._parse_element(x, ns) for x in all_subscriptions_xml]
return all_subscriptions

@classmethod
def _parse_element(cls, element, ns):
schedule_id = None
target = None

schedule_element = element.find('.//t:schedule', namespaces=ns)
content_element = element.find('.//t:content', namespaces=ns)
user_element = element.find('.//t:user', namespaces=ns)

if schedule_element is not None:
schedule_id = schedule_element.get('id', None)

if content_element is not None:
target = Target(content_element.get('id', None), content_element.get('type'))

if user_element is not None:
user_id = user_element.get('id')

id_ = element.get('id', None)
subject = element.get('subject', None)
sub = cls(subject, schedule_id, user_id, target)
sub._set_id(id_)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you have the id only settable via a private method? (If other Items do this to it's fine, just asking).
The other attributes are all available via __init__.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

modeled it after the UserItem -- because when creating a new User you don't have an id.

Same goes for Subscription.

return sub
5 changes: 3 additions & 2 deletions tableauserverclient/server/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@
from .sort import Sort
from .. import ConnectionItem, DatasourceItem,\
GroupItem, PaginationItem, ProjectItem, ScheduleItem, SiteItem, TableauAuth,\
UserItem, ViewItem, WorkbookItem, TaskItem
UserItem, ViewItem, WorkbookItem, TaskItem, SubscriptionItem
from .endpoint import Auth, Datasources, Endpoint, Groups, Projects, Schedules, \
Sites, Users, Views, Workbooks, ServerResponseError, MissingRequiredFieldError
Sites, Users, Views, Workbooks, Subscriptions, ServerResponseError, \
MissingRequiredFieldError
from .server import Server
from .pager import Pager
from .exceptions import NotSignedInError
1 change: 1 addition & 0 deletions tableauserverclient/server/endpoint/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@
from .users_endpoint import Users
from .views_endpoint import Views
from .workbooks_endpoint import Workbooks
from .subscriptions_endpoint import Subscriptions
53 changes: 53 additions & 0 deletions tableauserverclient/server/endpoint/subscriptions_endpoint.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
from .endpoint import Endpoint, api
from .exceptions import MissingRequiredFieldError
from .. import RequestFactory, SubscriptionItem, PaginationItem
import logging

logger = logging.getLogger('tableau.endpoint.subscriptions')


class Subscriptions(Endpoint):
@property
def baseurl(self):
return "{0}/sites/{1}/subscriptions".format(self.parent_srv.baseurl,
self.parent_srv.site_id)

@api(version='2.3')
def get(self, req_options=None):
logger.info('Querying all subscriptions for the site')
url = self.baseurl
server_response = self.get_request(url, req_options)

pagination_item = PaginationItem.from_response(server_response.content, self.parent_srv.namespace)
all_subscriptions = SubscriptionItem.from_response(server_response.content, self.parent_srv.namespace)
return all_subscriptions, pagination_item

@api(version='2.3')
def get_by_id(self, subscription_id):
if not subscription_id:
error = "No Subscription ID provided"
raise ValueError(error)
logger.info("Querying a single subscription by id ({})".format(subscription_id))
url = "{}/{}".format(self.baseurl, subscription_id)
server_response = self.get_request(url)
return SubscriptionItem.from_response(server_response.content, self.parent_srv.namespace)[0]

@api(version='2.3')
def create(self, subscription_item):
if not subscription_item:
error = "No Susbcription provided"
raise ValueError(error)
logger.info("Creating a subscription ({})".format(subscription_item))
url = self.baseurl
create_req = RequestFactory.Subscription.create_req(subscription_item)
server_response = self.post_request(url, create_req)
return SubscriptionItem.from_response(server_response.content, self.parent_srv.namespace)[0]

@api(version='2.3')
def delete(self, subscription_id):
if not subscription_id:
error = "Subscription ID undefined."
raise ValueError(error)
url = "{0}/{1}".format(self.baseurl, subscription_id)
self.delete_request(url)
logger.info('Deleted subscription (ID: {0})'.format(subscription_id))
19 changes: 19 additions & 0 deletions tableauserverclient/server/request_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,24 @@ def run_req(xml_request, task_item):
pass


class SubscriptionRequest(object):
def create_req(self, subscription_item):
xml_request = ET.Element('tsRequest')
subscription_element = ET.SubElement(xml_request, 'subscription')
subscription_element.attrib['subject'] = subscription_item.subject

content_element = ET.SubElement(subscription_element, 'content')
content_element.attrib['id'] = subscription_item.target.id
content_element.attrib['type'] = subscription_item.target.type

schedule_element = ET.SubElement(subscription_element, 'schedule')
schedule_element.attrib['id'] = subscription_item.schedule_id

user_element = ET.SubElement(subscription_element, 'user')
user_element.attrib['id'] = subscription_item.user_id
return ET.tostring(xml_request)


class RequestFactory(object):
Auth = AuthRequest()
Datasource = DatasourceRequest()
Expand All @@ -373,3 +391,4 @@ class RequestFactory(object):
User = UserRequest()
Workbook = WorkbookRequest()
WorkbookConnection = WorkbookConnection()
Subscription = SubscriptionRequest()
3 changes: 2 additions & 1 deletion tableauserverclient/server/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from .exceptions import NotSignedInError
from ..namespace import Namespace
from .endpoint import Sites, Views, Users, Groups, Workbooks, Datasources, Projects, Auth, \
Schedules, ServerInfo, Tasks, ServerInfoEndpointNotFoundError
Schedules, ServerInfo, Tasks, ServerInfoEndpointNotFoundError, Subscriptions

import requests

Expand Down Expand Up @@ -42,6 +42,7 @@ def __init__(self, server_address, use_server_version=False):
self.schedules = Schedules(self)
self.server_info = ServerInfo(self)
self.tasks = Tasks(self)
self.subscriptions = Subscriptions(self)
self._namespace = Namespace()

if use_server_version:
Expand Down
18 changes: 18 additions & 0 deletions test/assets/subscription_get.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?xml version='1.0' encoding='UTF-8'?>
<tsResponse
xmlns="http://tableau.com/api"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://tableau.com/api http://tableau.com/api/ts-api-2.6.xsd">
<pagination pageNumber="1" pageSize="100" totalAvailable="2" />
<subscriptions>
<subscription id="382e9a6e-0c08-4a95-b6c1-c14df7bac3e4" subject="Not Found Alert">
<content id="cdd716ca-5818-470e-8bec-086885dbadee" type="View" />
<schedule id="7617c389-cdca-4940-a66e-69956fcebf3e" name="Subscribe daily [00:00 - 04:00, Pacific US] [migrated at 1490824351877]" />
<user id="c0d5fc44-ad8c-4957-bec0-b70ed0f8df1e" name="jonsnow@winterfell.com" />
</subscription>
<subscription id="23cb7630-afc8-4c8e-b6cd-83ae0322ec66" subject="Last 7 Days">
<content id="2e6b4e8f-22dd-4061-8f75-bf33703da7e5" type="View" />
<schedule id="3407cd38-7b39-4983-86a6-67a1506a5e3f" name="SSS_27212a85-6b28-41f6-8c69-29b02043d7a5" />
<user id="c0d5fc44-ad8c-4957-bec0-b70ed0f8df1e" name="jonsnow@winterfell.com" />
</subscription>
</subscriptions>
</tsResponse>
10 changes: 10 additions & 0 deletions test/assets/subscription_get_by_id.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?xml version='1.0' encoding='UTF-8'?>
<tsResponse
xmlns="http://tableau.com/api"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://tableau.com/api http://tableau.com/api/ts-api-2.6.xsd">
<subscription id="382e9a6e-0c08-4a95-b6c1-c14df7bac3e4" subject="Not Found Alert">
<content id="cdd716ca-5818-470e-8bec-086885dbadee" type="View" />
<schedule id="7617c389-cdca-4940-a66e-69956fcebf3e" name="Subscribe daily [00:00 - 04:00, Pacific US] [migrated at 1490824351877]" />
<user id="c0d5fc44-ad8c-4957-bec0-b70ed0f8df1e" name="jonsnow@winterfell.com" />
</subscription>
</tsResponse>
CD09 50 changes: 50 additions & 0 deletions test/test_subscription.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import unittest
import os
import requests_mock
import tableauserverclient as TSC

TEST_ASSET_DIR = os.path.join(os.path.dirname(__file__), "assets")

GET_XML = os.path.join(TEST_ASSET_DIR, "subscription_get.xml")
GET_XML_BY_ID = os.path.join(TEST_ASSET_DIR, "subscription_get_by_id.xml")


class SubscriptionTests(unittest.TestCase):
def setUp(self):
self.server = TSC.Server("http://test")
self.server.version = '2.6'

# Fake Signin
self.server._site_id = "dad65087-b08b-4603-af4e-2887b8aafc67"
self.server._auth_token = "j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM"

self.baseurl = self.server.subscriptions.baseurl

def test_get_subscriptions(self):
with open(GET_XML, "rb") as f:
response_xml = f.read().decode("utf-8")
with requests_mock.mock() as m:
m.get(self.baseurl, text=response_xml)
all_subscriptions, pagination_item = self.server.subscriptions.get()

subscription = all_subscriptions[0]
self.assertEqual('382e9a6e-0c08-4a95-b6c1-c14df7bac3e4', subscription.id)
self.assertEqual('View', subscription.target.type)
self.assertEqual('cdd716ca-5818-470e-8bec-086885dbadee', subscription.target.id)
self.assertEqual('c0d5fc44-ad8c-4957-bec0-b70ed0f8df1e', subscription.user_id)
self.assertEqual('Not Found Alert', subscription.subject)
self.assertEqual('7617c389-cdca-4940-a66e-69956fcebf3e', subscription.schedule_id)

def test_get_subscription_by_id(self):
with open(GET_XML_BY_ID, "rb") as f:
response_xml = f.read().decode("utf-8")
with requests_mock.mock() as m:
m.get(self.baseurl + '/382e9a6e-0c08-4a95-b6c1-c14df7bac3e4', text=response_xml)
subscription = self.server.subscriptions.get_by_id('382e9a6e-0c08-4a95-b6c1-c14df7bac3e4')

self.assertEqual('382e9a6e-0c08-4a95-b6c1-c14df7bac3e4', subscription.id)
self.assertEqual('View', subscription.target.type)
self.assertEqual('cdd716ca-5818-470e-8bec-086885dbadee', subscription.target.id)
self.assertEqual('c0d5fc44-ad8c-4957-bec0-b70ed0f8df1e', subscription.user_id)
self.assertEqual('Not Found Alert', subscription.subject)
self.assertEqual('7617c389-cdca-4940-a66e-69956fcebf3e', subscription.schedule_id)
0