diff --git a/samples/explore_site.py b/samples/explore_site.py
new file mode 100644
index 000000000..8c4abd9d3
--- /dev/null
+++ b/samples/explore_site.py
@@ -0,0 +1,83 @@
+####
+# This script demonstrates how to use the Tableau Server Client
+# to interact with sites.
+####
+
+import argparse
+import logging
+import os.path
+import sys
+
+import tableauserverclient as TSC
+
+
+def main():
+
+ parser = argparse.ArgumentParser(description="Explore site updates by the Server API.")
+ # Common options; please keep those in sync across all samples
+ parser.add_argument("--server", "-s", required=True, help="server address")
+ parser.add_argument("--site", "-S", help="site name")
+ parser.add_argument(
+ "--token-name", "-p", required=True, help="name of the personal access token used to sign into the server"
+ )
+ parser.add_argument(
+ "--token-value", "-v", required=True, help="value of the personal access token used to sign into the server"
+ )
+ parser.add_argument(
+ "--logging-level",
+ "-l",
+ choices=["debug", "info", "error"],
+ default="error",
+ help="desired logging level (set to error by default)",
+ )
+
+ parser.add_argument("--delete")
+ parser.add_argument("--create")
+ parser.add_argument("--url")
+ parser.add_argument("--new_site_name")
+ parser.add_argument("--user_quota")
+ parser.add_argument("--storage_quota")
+ parser.add_argument("--status")
+
+ args = parser.parse_args()
+
+ # Set logging level based on user input, or error by default
+ logging_level = getattr(logging, args.logging_level.upper())
+ logging.basicConfig(level=logging_level)
+
+ # SIGN IN
+ tableau_auth = TSC.PersonalAccessTokenAuth(args.token_name, args.token_value, site_id=args.site)
+ server = TSC.Server(args.server, use_server_version=True)
+ new_site = None
+ with server.auth.sign_in(tableau_auth):
+ current_site = server.sites.get_by_id(server.site_id)
+
+ if args.delete:
+ print("You can only delete the site you are currently in")
+ print("Delete site `{}`?".format(current_site.name))
+ # server.sites.delete(server.site_id)
+
+ elif args.create:
+ new_site = TSC.SiteItem(args.create, args.url or args.create)
+ site_item = server.sites.create(new_site)
+ print(site_item)
+ # to do anything further with the site, you need to log into it
+ # if a PAT is required, that means going to the UI to create one
+
+ else:
+ new_site = current_site
+ print(current_site, "current user quota:", current_site.user_quota)
+ print("Remember, you can only update the site you are currently in")
+ if args.url:
+ new_site.content_url = args.url
+ if args.user_quota:
+ new_site.user_quota = args.user_quota
+ try:
+ updated_site = server.sites.update(new_site)
+ print(updated_site, "new user quota:", updated_site.user_quota)
+ except TSC.ServerResponseError as e:
+ print(e)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/samples/list.py b/samples/list.py
index 814c1b9ca..b5cdb38a5 100644
--- a/samples/list.py
+++ b/samples/list.py
@@ -59,7 +59,10 @@ def main():
count = 0
for resource in TSC.Pager(endpoint.get, options):
count = count + 1
- print(resource.id, resource.name)
+ # endpoint.populate_connections(resource)
+ print(resource.name[:18], " ") # , resource._connections())
+ if count > 100:
+ break
print("Total: {}".format(count))
diff --git a/setup.py b/setup.py
index 24d35250c..f9b072ec6 100644
--- a/setup.py
+++ b/setup.py
@@ -7,43 +7,44 @@
from distutils.core import setup
from os import path
+
this_directory = path.abspath(path.dirname(__file__))
-with open(path.join(this_directory, 'README.md'), encoding='utf-8') as f:
+with open(path.join(this_directory, "README.md"), encoding="utf-8") as f:
long_description = f.read()
# Only install pytest and runner when test command is run
# This makes work easier for offline installs or low bandwidth machines
-needs_pytest = {'pytest', 'test', 'ptr'}.intersection(sys.argv)
-pytest_runner = ['pytest-runner'] if needs_pytest else []
-test_requirements = ['black', 'mock', 'pytest', 'requests-mock>=1.0,<2.0', 'mypy>=0.920']
+needs_pytest = {"pytest", "test", "ptr"}.intersection(sys.argv)
+pytest_runner = ["pytest-runner"] if needs_pytest else []
+test_requirements = ["black", "mock", "pytest", "requests-mock>=1.0,<2.0", "mypy>=0.920"]
setup(
- name='tableauserverclient',
+ name="tableauserverclient",
version=versioneer.get_version(),
cmdclass=versioneer.get_cmdclass(),
- author='Tableau',
- author_email='github@tableau.com',
- url='https://github.com/tableau/server-client-python',
- package_data={'tableauserverclient':['py.typed']},
- packages=['tableauserverclient',
- 'tableauserverclient.helpers',
- 'tableauserverclient.models',
- 'tableauserverclient.server',
- 'tableauserverclient.server.endpoint'],
- license='MIT',
- description='A Python module for working with the Tableau Server REST API.',
+ author="Tableau",
+ author_email="github@tableau.com",
+ url="https://github.com/tableau/server-client-python",
+ package_data={"tableauserverclient": ["py.typed"]},
+ packages=[
+ "tableauserverclient",
+ "tableauserverclient.helpers",
+ "tableauserverclient.models",
+ "tableauserverclient.server",
+ "tableauserverclient.server.endpoint",
+ ],
+ license="MIT",
+ description="A Python module for working with the Tableau Server REST API.",
long_description=long_description,
- long_description_content_type='text/markdown',
- test_suite='test',
+ long_description_content_type="text/markdown",
+ test_suite="test",
setup_requires=pytest_runner,
install_requires=[
- 'defusedxml>=0.7.1',
- 'requests>=2.11,<3.0',
+ "defusedxml>=0.7.1",
+ "requests>=2.11,<3.0",
],
- python_requires='>3.7.0',
+ python_requires=">3.7.0",
tests_require=test_requirements,
- extras_require={
- 'test': test_requirements
- },
- zip_safe=False
+ extras_require={"test": test_requirements},
+ zip_safe=False,
)
diff --git a/tableauserverclient/datetime_helpers.py b/tableauserverclient/datetime_helpers.py
index 2b1df202c..0d968428d 100644
--- a/tableauserverclient/datetime_helpers.py
+++ b/tableauserverclient/datetime_helpers.py
@@ -1,12 +1,12 @@
import datetime
-# This code below is from the python documentation for
-# tzinfo: https://docs.python.org/2.3/lib/datetime-tzinfo.html
ZERO = datetime.timedelta(0)
HOUR = datetime.timedelta(hours=1)
+# This class is a concrete implementation of the abstract base class tzinfo
+# docs: https://docs.python.org/2.3/lib/datetime-tzinfo.html
class UTC(datetime.tzinfo):
"""UTC"""
diff --git a/tableauserverclient/models/site_item.py b/tableauserverclient/models/site_item.py
index 2d27acabf..3deda03e2 100644
--- a/tableauserverclient/models/site_item.py
+++ b/tableauserverclient/models/site_item.py
@@ -1,8 +1,8 @@
import warnings
import xml.etree.ElementTree as ET
+from distutils.version import Version
from defusedxml.ElementTree import fromstring
-
from .property_decorators import (
property_is_enum,
property_is_boolean,
@@ -14,7 +14,10 @@
VALID_CONTENT_URL_RE = r"^[a-zA-Z0-9_\-]*$"
-from typing import List, Optional, Union
+from typing import List, Optional, Union, TYPE_CHECKING
+
+if TYPE_CHECKING:
+ from tableauserverclient.server import Server
class SiteItem(object):
@@ -23,6 +26,19 @@ class SiteItem(object):
_tier_explorer_capacity: Optional[int] = None
_tier_viewer_capacity: Optional[int] = None
+ def __str__(self):
+ return (
+ "<"
+ + __name__
+ + ": "
+ + (self.name or "unnamed")
+ + ", "
+ + (self.id or "unknown-id")
+ + ", "
+ + (self.state or "unknown-state")
+ + ">"
+ )
+
class AdminMode:
ContentAndUsers: str = "ContentAndUsers"
ContentOnly: str = "ContentOnly"
@@ -261,6 +277,13 @@ def cataloging_enabled(self) -> bool:
def cataloging_enabled(self, value: bool):
self._cataloging_enabled = value
+ def is_default(self) -> bool:
+ return self.name.lower() == "default"
+
+ @staticmethod
+ def use_new_flow_settings(parent_srv: "Server") -> bool:
+ return parent_srv is not None and parent_srv.check_at_least_version("3.10")
+
@property
def flows_enabled(self) -> bool:
return self._flows_enabled
@@ -268,11 +291,10 @@ def flows_enabled(self) -> bool:
@flows_enabled.setter
@property_is_boolean
def flows_enabled(self, value: bool) -> None:
+ # Flows Enabled' is not a supported site setting in API Version [3.17].
+ # In Version 3.10+ use the more granular settings 'Editing Flows Enabled' and/or 'Scheduling Flows Enabled'
self._flows_enabled = value
- def is_default(self) -> bool:
- return self.name.lower() == "default"
-
@property
def editing_flows_enabled(self) -> bool:
return self._editing_flows_enabled
diff --git a/tableauserverclient/server/endpoint/endpoint.py b/tableauserverclient/server/endpoint/endpoint.py
index 0acc978d2..7e211cdf8 100644
--- a/tableauserverclient/server/endpoint/endpoint.py
+++ b/tableauserverclient/server/endpoint/endpoint.py
@@ -63,7 +63,7 @@ def _make_request(
logger.debug("request content: {}".format(helpers.strings.redact_xml(content[:1000])))
server_response = method(url, **parameters)
- self._check_status(server_response)
+ self._check_status(server_response, url)
loggable_response = self.log_response_safely(server_response)
logger.debug("Server response from {0}:\n\t{1}".format(url, loggable_response))
@@ -73,9 +73,9 @@ def _make_request(
return server_response
- def _check_status(self, server_response):
+ def _check_status(self, server_response, request_url=None):
if server_response.status_code >= 500:
- raise InternalServerError(server_response)
+ raise InternalServerError(server_response, request_url)
elif server_response.status_code not in Success_codes:
# todo: is an error reliably of content-type application/xml?
try:
@@ -112,7 +112,7 @@ def get_request(self, url, request_object=None, parameters=None):
if request_object is not None:
try:
# Query param delimiters don't need to be encoded for versions before 3.7 (2020.1)
- self.parent_srv.assert_at_least_version("3.7")
+ self.parent_srv.assert_at_least_version("3.7", "Query param encoding")
parameters = parameters or {}
parameters["params"] = request_object.get_query_params()
except EndpointUnavailableError:
@@ -182,7 +182,7 @@ def api(version):
def _decorator(func):
@wraps(func)
def wrapper(self, *args, **kwargs):
- self.parent_srv.assert_at_least_version(version)
+ self.parent_srv.assert_at_least_version(version, "endpoint")
return func(self, *args, **kwargs)
return wrapper
diff --git a/tableauserverclient/server/endpoint/exceptions.py b/tableauserverclient/server/endpoint/exceptions.py
index 34de00dd0..fb0f401c4 100644
--- a/tableauserverclient/server/endpoint/exceptions.py
+++ b/tableauserverclient/server/endpoint/exceptions.py
@@ -1,7 +1,11 @@
from defusedxml.ElementTree import fromstring
-class ServerResponseError(Exception):
+class TableauError(Exception):
+ pass
+
+
+class ServerResponseError(TableauError):
def __init__(self, code, summary, detail):
self.code = code
self.summary = summary
@@ -23,40 +27,41 @@ def from_response(cls, resp, ns):
return error_response
-class InternalServerError(Exception):
- def __init__(self, server_response):
+class InternalServerError(TableauError):
+ def __init__(self, server_response, request_url):
self.code = server_response.status_code
self.content = server_response.content
+ self.url = request_url or "server"
def __str__(self):
- return "\n\nError status code: {0}\n{1}".format(self.code, self.content)
+ return "\n\nInternal error {0} at {1}\n{2}".format(self.code, self.url, self.content)
-class MissingRequiredFieldError(Exception):
+class MissingRequiredFieldError(TableauError):
pass
-class ServerInfoEndpointNotFoundError(Exception):
+class ServerInfoEndpointNotFoundError(TableauError):
pass
-class EndpointUnavailableError(Exception):
+class EndpointUnavailableError(TableauError):
pass
-class ItemTypeNotAllowed(Exception):
+class ItemTypeNotAllowed(TableauError):
pass
-class NonXMLResponseError(Exception):
+class NonXMLResponseError(TableauError):
pass
-class InvalidGraphQLQuery(Exception):
+class InvalidGraphQLQuery(TableauError):
pass
-class GraphQLError(Exception):
+class GraphQLError(TableauError):
def __init__(self, error_payload):
self.error = error_payload
@@ -66,7 +71,7 @@ def __str__(self):
return pformat(self.error)
-class JobFailedException(Exception):
+class JobFailedException(TableauError):
def __init__(self, job):
self.notes = job.notes
self.job = job
@@ -79,7 +84,7 @@ class JobCancelledException(JobFailedException):
pass
-class FlowRunFailedException(Exception):
+class FlowRunFailedException(TableauError):
def __init__(self, flow_run):
self.background_job_id = flow_run.background_job_id
self.flow_run = flow_run
diff --git a/tableauserverclient/server/endpoint/jobs_endpoint.py b/tableauserverclient/server/endpoint/jobs_endpoint.py
index 99870ac34..6b709efad 100644
--- a/tableauserverclient/server/endpoint/jobs_endpoint.py
+++ b/tableauserverclient/server/endpoint/jobs_endpoint.py
@@ -29,7 +29,7 @@ def get(
if isinstance(job_id, RequestOptionsBase):
req_options = job_id
- self.parent_srv.assert_at_least_version("3.1")
+ self.parent_srv.assert_at_least_version("3.1", "Jobs.get_by_id(job_id)")
server_response = self.get_request(self.baseurl, req_options)
pagination_item = PaginationItem.from_response(server_response.content, self.parent_srv.namespace)
jobs = BackgroundJobItem.from_response(server_response.content, self.parent_srv.namespace)
diff --git a/tableauserverclient/server/endpoint/sites_endpoint.py b/tableauserverclient/server/endpoint/sites_endpoint.py
index bdf281fb9..67d7db209 100644
--- a/tableauserverclient/server/endpoint/sites_endpoint.py
+++ b/tableauserverclient/server/endpoint/sites_endpoint.py
@@ -22,6 +22,7 @@ def baseurl(self) -> str:
@api(version="2.0")
def get(self, req_options: Optional["RequestOptions"] = None) -> Tuple[List[SiteItem], PaginationItem]:
logger.info("Querying all sites on site")
+ logger.info("Requires Server Admin permissions")
url = self.baseurl
server_response = self.get_request(url, req_options)
pagination_item = PaginationItem.from_response(server_response.content, self.parent_srv.namespace)
@@ -34,6 +35,10 @@ def get_by_id(self, site_id: str) -> SiteItem:
if not site_id:
error = "Site ID undefined."
raise ValueError(error)
+ if not site_id == self.parent_srv.site_id:
+ error = "You can only retrieve the site for which you are currently authenticated."
+ raise ValueError(error)
+
logger.info("Querying single site (ID: {0})".format(site_id))
url = "{0}/{1}".format(self.baseurl, site_id)
server_response = self.get_request(url)
@@ -45,8 +50,10 @@ def get_by_name(self, site_name: str) -> SiteItem:
if not site_name:
error = "Site Name undefined."
raise ValueError(error)
+ print("Note: You can only work with the site for which you are currently authenticated")
logger.info("Querying single site (Name: {0})".format(site_name))
url = "{0}/{1}?key=name".format(self.baseurl, site_name)
+ print(self.baseurl, url)
server_response = self.get_request(url)
return SiteItem.from_response(server_response.content, self.parent_srv.namespace)[0]
@@ -56,7 +63,12 @@ def get_by_content_url(self, content_url: str) -> SiteItem:
if content_url is None:
error = "Content URL undefined."
raise ValueError(error)
+ if not self.parent_srv.baseurl.index(content_url) > 0:
+ error = "You can only work with the site you are currently authenticated for"
+ raise ValueError(error)
+
logger.info("Querying single site (Content URL: {0})".format(content_url))
+ logger.debug("Querying other sites requires Server Admin permissions")
url = "{0}/{1}?key=contentUrl".format(self.baseurl, content_url)
server_response = self.get_request(url)
return SiteItem.from_response(server_response.content, self.parent_srv.namespace)[0]
@@ -67,13 +79,18 @@ def update(self, site_item: SiteItem) -> SiteItem:
if not site_item.id:
error = "Site item missing ID."
raise MissingRequiredFieldError(error)
+ print(self.parent_srv.site_id, site_item.id)
+ if not site_item.id == self.parent_srv.site_id:
+ error = "You can only update the site you are currently authenticated for"
+ raise ValueError(error)
+
if site_item.admin_mode:
if site_item.admin_mode == SiteItem.AdminMode.ContentOnly and site_item.user_quota:
error = "You cannot set admin_mode to ContentOnly and also set a user quota"
raise ValueError(error)
url = "{0}/{1}".format(self.baseurl, site_item.id)
- update_req = RequestFactory.Site.update_req(site_item)
+ update_req = RequestFactory.Site.update_req(site_item, self.parent_srv)
server_response = self.put_request(url, update_req)
logger.info("Updated site item (ID: {0})".format(site_item.id))
update_site = copy.copy(site_item)
@@ -86,12 +103,11 @@ def delete(self, site_id: str) -> None:
error = "Site ID undefined."
raise ValueError(error)
url = "{0}/{1}".format(self.baseurl, site_id)
+ if not site_id == self.parent_srv.site_id:
+ error = "You can only delete the site you are currently authenticated for"
+ raise ValueError(error)
self.delete_request(url)
- # If we deleted the site we are logged into
- # then we are automatically logged out
- if site_id == self.parent_srv.site_id:
- logger.info("Deleting current site and clearing auth tokens")
- self.parent_srv._clear_auth()
+ self.parent_srv._clear_auth()
logger.info("Deleted single site (ID: {0}) and signed out".format(site_id))
# Create new site
@@ -103,7 +119,7 @@ def create(self, site_item: SiteItem) -> SiteItem:
raise ValueError(error)
url = self.baseurl
- create_req = RequestFactory.Site.create_req(site_item)
+ create_req = RequestFactory.Site.create_req(site_item, self.parent_srv)
server_response = self.post_request(url, create_req)
new_site = SiteItem.from_response(server_response.content, self.parent_srv.namespace)[0]
logger.info("Created new site (ID: {0})".format(new_site.id))
diff --git a/tableauserverclient/server/endpoint/tasks_endpoint.py b/tableauserverclient/server/endpoint/tasks_endpoint.py
index f147c79ae..a70480b91 100644
--- a/tableauserverclient/server/endpoint/tasks_endpoint.py
+++ b/tableauserverclient/server/endpoint/tasks_endpoint.py
@@ -25,7 +25,7 @@ def __normalize_task_type(self, task_type):
@api(version="2.6")
def get(self, req_options=None, task_type=TaskItem.Type.ExtractRefresh):
if task_type == TaskItem.Type.DataAcceleration:
- self.parent_srv.assert_at_least_version("3.8")
+ self.parent_srv.assert_at_least_version("3.8", "Data Acceleration Tasks")
logger.info("Querying all {} tasks for the site".format(task_type))
@@ -69,7 +69,7 @@ def run(self, task_item):
@api(version="3.6")
def delete(self, task_id, task_type=TaskItem.Type.ExtractRefresh):
if task_type == TaskItem.Type.DataAcceleration:
- self.parent_srv.assert_at_least_version("3.8")
+ self.parent_srv.assert_at_least_version("3.8", "Data Acceleration Tasks")
if not task_id:
error = "No Task ID provided"
diff --git a/tableauserverclient/server/request_factory.py b/tableauserverclient/server/request_factory.py
index fc00ca085..cf3161c41 100644
--- a/tableauserverclient/server/request_factory.py
+++ b/tableauserverclient/server/request_factory.py
@@ -25,6 +25,7 @@
from ..models import ConnectionItem
from ..models import SiteItem
from ..models import ProjectItem
+ from tableauserverclient.server import Server
def _add_multipart(parts: Dict) -> Tuple[Any, str]:
@@ -556,7 +557,7 @@ def _add_to_req(self, id_: Optional[str], target_type: str, task_type: str = Tas
"""
if not isinstance(id_, str):
- raise ValueError(f"id_ should be a string, reeceived: {type(id_)}")
+ raise ValueError(f"id_ should be a string, received: {type(id_)}")
xml_request = ET.Element("tsRequest")
task_element = ET.SubElement(xml_request, "task")
task = ET.SubElement(task_element, task_type)
@@ -576,7 +577,7 @@ def add_flow_req(self, id_: Optional[str], task_type: str = TaskItem.Type.RunFlo
class SiteRequest(object):
- def update_req(self, site_item: "SiteItem"):
+ def update_req(self, site_item: "SiteItem", parent_srv: "Server" = None):
xml_request = ET.Element("tsRequest")
site_element = ET.SubElement(xml_request, "site")
if site_item.name:
@@ -601,14 +602,15 @@ def update_req(self, site_item: "SiteItem"):
site_element.attrib["revisionHistoryEnabled"] = str(site_item.revision_history_enabled).lower()
if site_item.data_acceleration_mode is not None:
site_element.attrib["dataAccelerationMode"] = str(site_item.data_acceleration_mode).lower()
- if site_item.flows_enabled is not None:
- site_element.attrib["flowsEnabled"] = str(site_item.flows_enabled).lower()
if site_item.cataloging_enabled is not None:
site_element.attrib["catalogingEnabled"] = str(site_item.cataloging_enabled).lower()
- if site_item.editing_flows_enabled is not None:
- site_element.attrib["editingFlowsEnabled"] = str(site_item.editing_flows_enabled).lower()
- if site_item.scheduling_flows_enabled is not None:
- site_element.attrib["schedulingFlowsEnabled"] = str(site_item.scheduling_flows_enabled).lower()
+
+ flows_edit = str(site_item.editing_flows_enabled).lower()
+ flows_schedule = str(site_item.scheduling_flows_enabled).lower()
+ flows_all = str(site_item.flows_enabled).lower()
+
+ self.set_versioned_flow_attributes(flows_all, flows_edit, flows_schedule, parent_srv, site_element, site_item)
+
if site_item.allow_subscription_attachments is not None:
site_element.attrib["allowSubscriptionAttachments"] = str(site_item.allow_subscription_attachments).lower()
if site_item.guest_access_enabled is not None:
@@ -682,7 +684,8 @@ def update_req(self, site_item: "SiteItem"):
return ET.tostring(xml_request)
- def create_req(self, site_item: "SiteItem"):
+ # server: the site request model changes based on api version
+ def create_req(self, site_item: "SiteItem", parent_srv: "Server" = None):
xml_request = ET.Element("tsRequest")
site_element = ET.SubElement(xml_request, "site")
site_element.attrib["name"] = site_item.name
@@ -701,12 +704,13 @@ def create_req(self, site_item: "SiteItem"):
site_element.attrib["revisionLimit"] = str(site_item.revision_limit)
if site_item.data_acceleration_mode is not None:
site_element.attrib["dataAccelerationMode"] = str(site_item.data_acceleration_mode).lower()
- if site_item.flows_enabled is not None:
- site_element.attrib["flowsEnabled"] = str(site_item.flows_enabled).lower()
- if site_item.editing_flows_enabled is not None:
- site_element.attrib["editingFlowsEnabled"] = str(site_item.editing_flows_enabled).lower()
- if site_item.scheduling_flows_enabled is not None:
- site_element.attrib["schedulingFlowsEnabled"] = str(site_item.scheduling_flows_enabled).lower()
+
+ flows_edit = str(site_item.editing_flows_enabled).lower()
+ flows_schedule = str(site_item.scheduling_flows_enabled).lower()
+ flows_all = str(site_item.flows_enabled).lower()
+
+ self.set_versioned_flow_attributes(flows_all, flows_edit, flows_schedule, parent_srv, site_element, site_item)
+
if site_item.allow_subscription_attachments is not None:
site_element.attrib["allowSubscriptionAttachments"] = str(site_item.allow_subscription_attachments).lower()
if site_item.guest_access_enabled is not None:
@@ -784,6 +788,32 @@ def create_req(self, site_item: "SiteItem"):
return ET.tostring(xml_request)
+ def set_versioned_flow_attributes(self, flows_all, flows_edit, flows_schedule, parent_srv, site_element, site_item):
+ if (not parent_srv) or SiteItem.use_new_flow_settings(parent_srv):
+ if site_item.flows_enabled is not None:
+ flows_edit = flows_edit or flows_all
+ flows_schedule = flows_schedule or flows_all
+ import warnings
+
+ warnings.warn(
+ "FlowsEnabled has been removed and become two options:"
+ " SchedulingFlowsEnabled and EditingFlowsEnabled"
+ )
+ if site_item.editing_flows_enabled is not None:
+ site_element.attrib["editingFlowsEnabled"] = flows_edit
+ if site_item.scheduling_flows_enabled is not None:
+ site_element.attrib["schedulingFlowsEnabled"] = flows_schedule
+
+ else:
+ if site_item.flows_enabled is not None:
+ site_element.attrib["flowsEnabled"] = str(site_item.flows_enabled).lower()
+ if site_item.editing_flows_enabled is not None or site_item.scheduling_flows_enabled is not None:
+ flows_all = flows_all or flows_edit or flows_schedule
+ site_element.attrib["flowsEnabled"] = flows_all
+ import warnings
+
+ warnings.warn("In version 3.10 and earlier there is only one option: FlowsEnabled")
+
class TableRequest(object):
def update_req(self, table_item):
diff --git a/tableauserverclient/server/server.py b/tableauserverclient/server/server.py
index 4522bc272..8ac8c9ef2 100644
--- a/tableauserverclient/server/server.py
+++ b/tableauserverclient/server/server.py
@@ -1,6 +1,5 @@
from distutils.version import LooseVersion as Version
import urllib3
-import requests
from defusedxml.ElementTree import fromstring
from .endpoint import (
@@ -30,8 +29,8 @@
Metrics,
)
from .endpoint.exceptions import (
- EndpointUnavailableError,
ServerInfoEndpointNotFoundError,
+ EndpointUnavailableError,
)
from .exceptions import NotSignedInError
from ..namespace import Namespace
@@ -144,13 +143,14 @@ def use_highest_version(self):
warnings.warn("use use_server_version instead", DeprecationWarning)
- def assert_at_least_version(self, version):
+ def check_at_least_version(self, target: str):
server_version = Version(self.version or "0.0")
- minimum_supported = Version(version)
- if server_version < minimum_supported:
- error = "This endpoint is not available in API version {}. Requires {}".format(
- server_version, minimum_supported
- )
+ target_version = Version(target)
+ return server_version >= target_version
+
+ def assert_at_least_version(self, comparison: str, reason: str):
+ if not self.check_at_least_version(comparison):
+ error = "{} is not available in API version {}. Requires {}".format(reason, self.version, comparison)
raise EndpointUnavailableError(error)
@property
diff --git a/test/assets/site_get_by_id.xml b/test/assets/site_get_by_id.xml
index a47703fb6..a8a1e9a5c 100644
--- a/test/assets/site_get_by_id.xml
+++ b/test/assets/site_get_by_id.xml
@@ -1,4 +1,4 @@
-
-
\ No newline at end of file
+
+
diff --git a/test/assets/site_get_by_name.xml b/test/assets/site_get_by_name.xml
index 852f9594f..b7ae2b595 100644
--- a/test/assets/site_get_by_name.xml
+++ b/test/assets/site_get_by_name.xml
@@ -1,4 +1,4 @@
-
-
\ No newline at end of file
+
+
diff --git a/test/assets/site_update.xml b/test/assets/site_update.xml
index dbb166de1..1661a426b 100644
--- a/test/assets/site_update.xml
+++ b/test/assets/site_update.xml
@@ -1,4 +1,4 @@
-
-
\ No newline at end of file
+
+
diff --git a/test/test_site.py b/test/test_site.py
index 23eb99ddd..b8469e56c 100644
--- a/test/test_site.py
+++ b/test/test_site.py
@@ -24,6 +24,9 @@ def setUp(self) -> None:
self.server._site_id = "0626857c-1def-4503-a7d8-7907c3ff9d9f"
self.baseurl = self.server.sites.baseurl
+ # sites APIs can only be called on the site being logged in to
+ self.logged_in_site = self.server.site_id
+
def test_get(self) -> None:
with open(GET_XML, "rb") as f:
response_xml = f.read().decode("utf-8")
@@ -71,10 +74,10 @@ def test_get_by_id(self) -> None:
with open(GET_BY_ID_XML, "rb") as f:
response_xml = f.read().decode("utf-8")
with requests_mock.mock() as m:
- m.get(self.baseurl + "/dad65087-b08b-4603-af4e-2887b8aafc67", text=response_xml)
- single_site = self.server.sites.get_by_id("dad65087-b08b-4603-af4e-2887b8aafc67")
+ m.get(self.baseurl + "/" + self.logged_in_site, text=response_xml)
+ single_site = self.server.sites.get_by_id(self.logged_in_site)
- self.assertEqual("dad65087-b08b-4603-af4e-2887b8aafc67", single_site.id)
+ self.assertEqual(self.logged_in_site, single_site.id)
self.assertEqual("Active", single_site.state)
self.assertEqual("Default", single_site.name)
self.assertEqual("ContentOnly", single_site.admin_mode)
@@ -95,7 +98,7 @@ def test_get_by_name(self) -> None:
m.get(self.baseurl + "/testsite?key=name", text=response_xml)
single_site = self.server.sites.get_by_name("testsite")
- self.assertEqual("dad65087-b08b-4603-af4e-2887b8aafc67", single_site.id)
+ self.assertEqual(self.logged_in_site, single_site.id)
self.assertEqual("Active", single_site.state)
self.assertEqual("testsite", single_site.name)
self.assertEqual("ContentOnly", single_site.admin_mode)
@@ -110,7 +113,7 @@ def test_update(self) -> None:
with open(UPDATE_XML, "rb") as f:
response_xml = f.read().decode("utf-8")
with requests_mock.mock() as m:
- m.put(self.baseurl + "/6b7179ba-b82b-4f0f-91ed-812074ac5da6", text=response_xml)
+ m.put(self.baseurl + "/" + self.logged_in_site, text=response_xml)
single_site = TSC.SiteItem(
name="Tableau",
content_url="tableau",
@@ -143,10 +146,11 @@ def test_update(self) -> None:
tier_explorer_capacity=5,
tier_viewer_capacity=5,
)
- single_site._id = "6b7179ba-b82b-4f0f-91ed-812074ac5da6"
+ single_site._id = self.logged_in_site
+ self.server.sites.parent_srv = self.server
single_site = self.server.sites.update(single_site)
- self.assertEqual("6b7179ba-b82b-4f0f-91ed-812074ac5da6", single_site.id)
+ self.assertEqual(self.logged_in_site, single_site.id)
self.assertEqual("tableau", single_site.content_url)
self.assertEqual("Suspended", single_site.state)
self.assertEqual("Tableau", single_site.name)
diff --git a/versioneer.py b/versioneer.py
index 59211ed6f..86c240e13 100755
--- a/versioneer.py
+++ b/versioneer.py
@@ -277,6 +277,7 @@
"""
from __future__ import print_function
+
try:
import configparser
except ImportError:
@@ -308,11 +309,13 @@ def get_root():
setup_py = os.path.join(root, "setup.py")
versioneer_py = os.path.join(root, "versioneer.py")
if not (os.path.exists(setup_py) or os.path.exists(versioneer_py)):
- err = ("Versioneer was unable to run the project root directory. "
- "Versioneer requires setup.py to be executed from "
- "its immediate directory (like 'python setup.py COMMAND'), "
- "or in a way that lets it use sys.argv[0] to find the root "
- "(like 'python path/to/setup.py COMMAND').")
+ err = (
+ "Versioneer was unable to run the project root directory. "
+ "Versioneer requires setup.py to be executed from "
+ "its immediate directory (like 'python setup.py COMMAND'), "
+ "or in a way that lets it use sys.argv[0] to find the root "
+ "(like 'python path/to/setup.py COMMAND')."
+ )
raise VersioneerBadRootError(err)
try:
# Certain runtime workflows (setup.py install/develop in a setuptools
@@ -325,8 +328,7 @@ def get_root():
me_dir = os.path.normcase(os.path.splitext(me)[0])
vsr_dir = os.path.normcase(os.path.splitext(versioneer_py)[0])
if me_dir != vsr_dir:
- print("Warning: build in %s is using versioneer.py from %s"
- % (os.path.dirname(me), versioneer_py))
+ print("Warning: build in %s is using versioneer.py from %s" % (os.path.dirname(me), versioneer_py))
except NameError:
pass
return root
@@ -348,6 +350,7 @@ def get(parser, name):
if parser.has_option("versioneer", name):
return parser.get("versioneer", name)
return None
+
cfg = VersioneerConfig()
cfg.VCS = VCS
cfg.style = get(parser, "style") or ""
@@ -372,17 +375,18 @@ class NotThisMethod(Exception):
def register_vcs_handler(vcs, method): # decorator
"""Decorator to mark a method as the handler for a particular VCS."""
+
def decorate(f):
"""Store f in HANDLERS[vcs][method]."""
if vcs not in HANDLERS:
HANDLERS[vcs] = {}
HANDLERS[vcs][method] = f
return f
+
return decorate
-def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False,
- env=None):
+def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, env=None):
"""Call the given command(s)."""
assert isinstance(commands, list)
p = None
@@ -390,10 +394,9 @@ def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False,
try:
dispcmd = str([c] + args)
# remember shell=False, so use git.cmd on windows, not just git
- p = subprocess.Popen([c] + args, cwd=cwd, env=env,
- stdout=subprocess.PIPE,
- stderr=(subprocess.PIPE if hide_stderr
- else None))
+ p = subprocess.Popen(
+ [c] + args, cwd=cwd, env=env, stdout=subprocess.PIPE, stderr=(subprocess.PIPE if hide_stderr else None)
+ )
break
except EnvironmentError:
e = sys.exc_info()[1]
@@ -418,7 +421,9 @@ def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False,
return stdout, p.returncode
-LONG_VERSION_PY['git'] = '''
+LONG_VERSION_PY[
+ "git"
+] = '''
# This file helps to compute a version number in source trees obtained from
# git-archive tarball (such as those provided by githubs download-from-tag
# feature). Distribution tarballs (built by setup.py sdist) and build
@@ -993,7 +998,7 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose):
# starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of
# just "foo-1.0". If we see a "tag: " prefix, prefer those.
TAG = "tag: "
- tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)])
+ tags = set([r[len(TAG) :] for r in refs if r.startswith(TAG)])
if not tags:
# Either we're using git < 1.8.3, or there really are no tags. We use
# a heuristic: assume all version tags have a digit. The old git %d
@@ -1002,7 +1007,7 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose):
# between branches and tags. By ignoring refnames without digits, we
# filter out many common branch names like "release" and
# "stabilization", as well as "HEAD" and "master".
- tags = set([r for r in refs if re.search(r'\d', r)])
+ tags = set([r for r in refs if re.search(r"\d", r)])
if verbose:
print("discarding '%s', no digits" % ",".join(refs - tags))
if verbose:
@@ -1010,19 +1015,26 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose):
for ref in sorted(tags):
# sorting will prefer e.g. "2.0" over "2.0rc1"
if ref.startswith(tag_prefix):
- r = ref[len(tag_prefix):]
+ r = ref[len(tag_prefix) :]
if verbose:
print("picking %s" % r)
- return {"version": r,
- "full-revisionid": keywords["full"].strip(),
- "dirty": False, "error": None,
- "date": date}
+ return {
+ "version": r,
+ "full-revisionid": keywords["full"].strip(),
+ "dirty": False,
+ "error": None,
+ "date": date,
+ }
# no suitable tags, so version is "0+unknown", but full hex is still there
if verbose:
print("no suitable tags, using unknown + full revision id")
- return {"version": "0+unknown",
- "full-revisionid": keywords["full"].strip(),
- "dirty": False, "error": "no suitable tags", "date": None}
+ return {
+ "version": "0+unknown",
+ "full-revisionid": keywords["full"].strip(),
+ "dirty": False,
+ "error": "no suitable tags",
+ "date": None,
+ }
@register_vcs_handler("git", "pieces_from_vcs")
@@ -1037,8 +1049,7 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
if sys.platform == "win32":
GITS = ["git.cmd", "git.exe"]
- out, rc = run_command(GITS, ["rev-parse", "--git-dir"], cwd=root,
- hide_stderr=True)
+ out, rc = run_command(GITS, ["rev-parse", "--git-dir"], cwd=root, hide_stderr=True)
if rc != 0:
if verbose:
print("Directory %s not under git control" % root)
@@ -1046,10 +1057,9 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
# if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty]
# if there isn't one, this yields HEX[-dirty] (no NUM)
- describe_out, rc = run_command(GITS, ["describe", "--tags", "--dirty",
- "--always", "--long",
- "--match", "%s*" % tag_prefix],
- cwd=root)
+ describe_out, rc = run_command(
+ GITS, ["describe", "--tags", "--dirty", "--always", "--long", "--match", "%s*" % tag_prefix], cwd=root
+ )
# --long was added in git-1.5.5
if describe_out is None:
raise NotThisMethod("'git describe' failed")
@@ -1072,17 +1082,16 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
dirty = git_describe.endswith("-dirty")
pieces["dirty"] = dirty
if dirty:
- git_describe = git_describe[:git_describe.rindex("-dirty")]
+ git_describe = git_describe[: git_describe.rindex("-dirty")]
# now we have TAG-NUM-gHEX or HEX
if "-" in git_describe:
# TAG-NUM-gHEX
- mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe)
+ mo = re.search(r"^(.+)-(\d+)-g([0-9a-f]+)$", git_describe)
if not mo:
# unparseable. Maybe git-describe is misbehaving?
- pieces["error"] = ("unable to parse git-describe output: '%s'"
- % describe_out)
+ pieces["error"] = "unable to parse git-describe output: '%s'" % describe_out
return pieces
# tag
@@ -1091,10 +1100,9 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
if verbose:
fmt = "tag '%s' doesn't start with prefix '%s'"
print(fmt % (full_tag, tag_prefix))
- pieces["error"] = ("tag '%s' doesn't start with prefix '%s'"
- % (full_tag, tag_prefix))
+ pieces["error"] = "tag '%s' doesn't start with prefix '%s'" % (full_tag, tag_prefix)
return pieces
- pieces["closest-tag"] = full_tag[len(tag_prefix):]
+ pieces["closest-tag"] = full_tag[len(tag_prefix) :]
# distance: number of commits since tag
pieces["distance"] = int(mo.group(2))
@@ -1105,13 +1113,11 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
else:
# HEX: no tags
pieces["closest-tag"] = None
- count_out, rc = run_command(GITS, ["rev-list", "HEAD", "--count"],
- cwd=root)
+ count_out, rc = run_command(GITS, ["rev-list", "HEAD", "--count"], cwd=root)
pieces["distance"] = int(count_out) # total number of commits
# commit date: see ISO-8601 comment in git_versions_from_keywords()
- date = run_command(GITS, ["show", "-s", "--format=%ci", "HEAD"],
- cwd=root)[0].strip()
+ date = run_command(GITS, ["show", "-s", "--format=%ci", "HEAD"], cwd=root)[0].strip()
pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1)
return pieces
@@ -1167,16 +1173,19 @@ def versions_from_parentdir(parentdir_prefix, root, verbose):
for i in range(3):
dirname = os.path.basename(root)
if dirname.startswith(parentdir_prefix):
- return {"version": dirname[len(parentdir_prefix):],
- "full-revisionid": None,
- "dirty": False, "error": None, "date": None}
+ return {
+ "version": dirname[len(parentdir_prefix) :],
+ "full-revisionid": None,
+ "dirty": False,
+ "error": None,
+ "date": None,
+ }
else:
rootdirs.append(root)
root = os.path.dirname(root) # up a level
if verbose:
- print("Tried directories %s but none started with prefix %s" %
- (str(rootdirs), parentdir_prefix))
+ print("Tried directories %s but none started with prefix %s" % (str(rootdirs), parentdir_prefix))
raise NotThisMethod("rootdir doesn't start with parentdir_prefix")
@@ -1205,11 +1214,9 @@ def versions_from_file(filename):
contents = f.read()
except EnvironmentError:
raise NotThisMethod("unable to read _version.py")
- mo = re.search(r"version_json = '''\n(.*)''' # END VERSION_JSON",
- contents, re.M | re.S)
+ mo = re.search(r"version_json = '''\n(.*)''' # END VERSION_JSON", contents, re.M | re.S)
if not mo:
- mo = re.search(r"version_json = '''\r\n(.*)''' # END VERSION_JSON",
- contents, re.M | re.S)
+ mo = re.search(r"version_json = '''\r\n(.*)''' # END VERSION_JSON", contents, re.M | re.S)
if not mo:
raise NotThisMethod("no version_json in _version.py")
return json.loads(mo.group(1))
@@ -1218,8 +1225,7 @@ def versions_from_file(filename):
def write_to_version_file(filename, versions):
"""Write the given version number to the given _version.py file."""
os.unlink(filename)
- contents = json.dumps(versions, sort_keys=True,
- indent=1, separators=(",", ": "))
+ contents = json.dumps(versions, sort_keys=True, indent=1, separators=(",", ": "))
with open(filename, "w") as f:
f.write(SHORT_VERSION_PY % contents)
@@ -1251,8 +1257,7 @@ def render_pep440(pieces):
rendered += ".dirty"
else:
# exception #1
- rendered = "0+untagged.%d.g%s" % (pieces["distance"],
- pieces["short"])
+ rendered = "0+untagged.%d.g%s" % (pieces["distance"], pieces["short"])
if pieces["dirty"]:
rendered += ".dirty"
return rendered
@@ -1366,11 +1371,13 @@ def render_git_describe_long(pieces):
def render(pieces, style):
"""Render the given version pieces into the requested style."""
if pieces["error"]:
- return {"version": "unknown",
- "full-revisionid": pieces.get("long"),
- "dirty": None,
- "error": pieces["error"],
- "date": None}
+ return {
+ "version": "unknown",
+ "full-revisionid": pieces.get("long"),
+ "dirty": None,
+ "error": pieces["error"],
+ "date": None,
+ }
if not style or style == "default":
style = "pep440" # the default
@@ -1390,9 +1397,13 @@ def render(pieces, style):
else:
raise ValueError("unknown style '%s'" % style)
- return {"version": rendered, "full-revisionid": pieces["long"],
- "dirty": pieces["dirty"], "error": None,
- "date": pieces.get("date")}
+ return {
+ "version": rendered,
+ "full-revisionid": pieces["long"],
+ "dirty": pieces["dirty"],
+ "error": None,
+ "date": pieces.get("date"),
+ }
class VersioneerBadRootError(Exception):
@@ -1415,8 +1426,7 @@ def get_versions(verbose=False):
handlers = HANDLERS.get(cfg.VCS)
assert handlers, "unrecognized VCS '%s'" % cfg.VCS
verbose = verbose or cfg.verbose
- assert cfg.versionfile_source is not None, \
- "please set versioneer.versionfile_source"
+ assert cfg.versionfile_source is not None, "please set versioneer.versionfile_source"
assert cfg.tag_prefix is not None, "please set versioneer.tag_prefix"
versionfile_abs = os.path.join(root, cfg.versionfile_source)
@@ -1470,9 +1480,13 @@ def get_versions(verbose=False):
if verbose:
print("unable to compute version")
- return {"version": "0+unknown", "full-revisionid": None,
- "dirty": None, "error": "unable to compute version",
- "date": None}
+ return {
+ "version": "0+unknown",
+ "full-revisionid": None,
+ "dirty": None,
+ "error": "unable to compute version",
+ "date": None,
+ }
def get_version():
@@ -1521,6 +1535,7 @@ def run(self):
print(" date: %s" % vers.get("date"))
if vers["error"]:
print(" error: %s" % vers["error"])
+
cmds["version"] = cmd_version
# we override "build_py" in both distutils and setuptools
@@ -1553,14 +1568,15 @@ def run(self):
# now locate _version.py in the new build/ directory and replace
# it with an updated value
if cfg.versionfile_build:
- target_versionfile = os.path.join(self.build_lib,
- cfg.versionfile_build)
+ target_versionfile = os.path.join(self.build_lib, cfg.versionfile_build)
print("UPDATING %s" % target_versionfile)
write_to_version_file(target_versionfile, versions)
+
cmds["build_py"] = cmd_build_py
if "cx_Freeze" in sys.modules: # cx_freeze enabled?
from cx_Freeze.dist import build_exe as _build_exe
+
# nczeczulin reports that py2exe won't like the pep440-style string
# as FILEVERSION, but it can be used for PRODUCTVERSION, e.g.
# setup(console=[{
@@ -1581,17 +1597,21 @@ def run(self):
os.unlink(target_versionfile)
with open(cfg.versionfile_source, "w") as f:
LONG = LONG_VERSION_PY[cfg.VCS]
- f.write(LONG %
- {"DOLLAR": "$",
- "STYLE": cfg.style,
- "TAG_PREFIX": cfg.tag_prefix,
- "PARENTDIR_PREFIX": cfg.parentdir_prefix,
- "VERSIONFILE_SOURCE": cfg.versionfile_source,
- })
+ f.write(
+ LONG
+ % {
+ "DOLLAR": "$",
+ "STYLE": cfg.style,
+ "TAG_PREFIX": cfg.tag_prefix,
+ "PARENTDIR_PREFIX": cfg.parentdir_prefix,
+ "VERSIONFILE_SOURCE": cfg.versionfile_source,
+ }
+ )
+
cmds["build_exe"] = cmd_build_exe
del cmds["build_py"]
- if 'py2exe' in sys.modules: # py2exe enabled?
+ if "py2exe" in sys.modules: # py2exe enabled?
try:
from py2exe.distutils_buildexe import py2exe as _py2exe # py3
except ImportError:
@@ -1610,13 +1630,17 @@ def run(self):
os.unlink(target_versionfile)
with open(cfg.versionfile_source, "w") as f:
LONG = LONG_VERSION_PY[cfg.VCS]
- f.write(LONG %
- {"DOLLAR": "$",
- "STYLE": cfg.style,
- "TAG_PREFIX": cfg.tag_prefix,
- "PARENTDIR_PREFIX": cfg.parentdir_prefix,
- "VERSIONFILE_SOURCE": cfg.versionfile_source,
- })
+ f.write(
+ LONG
+ % {
+ "DOLLAR": "$",
+ "STYLE": cfg.style,
+ "TAG_PREFIX": cfg.tag_prefix,
+ "PARENTDIR_PREFIX": cfg.parentdir_prefix,
+ "VERSIONFILE_SOURCE": cfg.versionfile_source,
+ }
+ )
+
cmds["py2exe"] = cmd_py2exe
# we override different "sdist" commands for both environments
@@ -1643,8 +1667,8 @@ def make_release_tree(self, base_dir, files):
# updated value
target_versionfile = os.path.join(base_dir, cfg.versionfile_source)
print("UPDATING %s" % target_versionfile)
- write_to_version_file(target_versionfile,
- self._versioneer_generated_versions)
+ write_to_version_file(target_versionfile, self._versioneer_generated_versions)
+
cmds["sdist"] = cmd_sdist
return cmds
@@ -1699,11 +1723,9 @@ def do_setup():
root = get_root()
try:
cfg = get_config_from_root(root)
- except (EnvironmentError, configparser.NoSectionError,
- configparser.NoOptionError) as e:
+ except (EnvironmentError, configparser.NoSectionError, configparser.NoOptionError) as e:
if isinstance(e, (EnvironmentError, configparser.NoSectionError)):
- print("Adding sample versioneer config to setup.cfg",
- file=sys.stderr)
+ print("Adding sample versioneer config to setup.cfg", file=sys.stderr)
with open(os.path.join(root, "setup.cfg"), "a") as f:
f.write(SAMPLE_CONFIG)
print(CONFIG_ERROR, file=sys.stderr)
@@ -1712,15 +1734,18 @@ def do_setup():
print(" creating %s" % cfg.versionfile_source)
with open(cfg.versionfile_source, "w") as f:
LONG = LONG_VERSION_PY[cfg.VCS]
- f.write(LONG % {"DOLLAR": "$",
- "STYLE": cfg.style,
- "TAG_PREFIX": cfg.tag_prefix,
- "PARENTDIR_PREFIX": cfg.parentdir_prefix,
- "VERSIONFILE_SOURCE": cfg.versionfile_source,
- })
-
- ipy = os.path.join(os.path.dirname(cfg.versionfile_source),
- "__init__.py")
+ f.write(
+ LONG
+ % {
+ "DOLLAR": "$",
+ "STYLE": cfg.style,
+ "TAG_PREFIX": cfg.tag_prefix,
+ "PARENTDIR_PREFIX": cfg.parentdir_prefix,
+ "VERSIONFILE_SOURCE": cfg.versionfile_source,
+ }
+ )
+
+ ipy = os.path.join(os.path.dirname(cfg.versionfile_source), "__init__.py")
if os.path.exists(ipy):
try:
with open(ipy, "r") as f:
@@ -1762,8 +1787,7 @@ def do_setup():
else:
print(" 'versioneer.py' already in MANIFEST.in")
if cfg.versionfile_source not in simple_includes:
- print(" appending versionfile_source ('%s') to MANIFEST.in" %
- cfg.versionfile_source)
+ print(" appending versionfile_source ('%s') to MANIFEST.in" % cfg.versionfile_source)
with open(manifest_in, "a") as f:
f.write("include %s\n" % cfg.versionfile_source)
else: