1+ import io
12import logging
2- from typing import List , Optional , Tuple
3-
4- from .endpoint import QuerysetEndpoint , api
5- from .exceptions import MissingRequiredFieldError
3+ import os
4+ from pathlib import Path
5+ from typing import List , Optional , Tuple , Union
6+
7+ from tableauserverclient .config import BYTES_PER_MB , FILESIZE_LIMIT_MB
8+ from tableauserverclient .filesys_helpers import get_file_object_size
9+ from tableauserverclient .server .endpoint .endpoint import QuerysetEndpoint , api
10+ from tableauserverclient .server .endpoint .exceptions import MissingRequiredFieldError
611from tableauserverclient .models import CustomViewItem , PaginationItem
712from tableauserverclient .server import RequestFactory , RequestOptions , ImageRequestOptions
813
1621update the name or owner of a custom view.
1722"""
1823
24+ FilePath = Union [str , os .PathLike ]
25+ FileObject = Union [io .BufferedReader , io .BytesIO ]
26+ FileObjectR = Union [io .BufferedReader , io .BytesIO ]
27+ FileObjectW = Union [io .BufferedWriter , io .BytesIO ]
28+ PathOrFileR = Union [FilePath , FileObjectR ]
29+ PathOrFileW = Union [FilePath , FileObjectW ]
30+ io_types_r = (io .BufferedReader , io .BytesIO )
31+ io_types_w = (io .BufferedWriter , io .BytesIO )
32+
1933
2034class CustomViews (QuerysetEndpoint [CustomViewItem ]):
2135 def __init__ (self , parent_srv ):
@@ -25,6 +39,10 @@ def __init__(self, parent_srv):
2539 def baseurl (self ) -> str :
2640 return "{0}/sites/{1}/customviews" .format (self .parent_srv .baseurl , self .parent_srv .site_id )
2741
42+ @property
43+ def expurl (self ) -> str :
44+ return f"{ self .parent_srv ._server_address } /api/exp/sites/{ self .parent_srv .site_id } /customviews"
45+
2846 """
2947 If the request has no filter parameters: Administrators will see all custom views.
3048 Other users will see only custom views that they own.
@@ -102,3 +120,46 @@ def delete(self, view_id: str) -> None:
102120 url = "{0}/{1}" .format (self .baseurl , view_id )
103121 self .delete_request (url )
104122 logger .info ("Deleted single custom view (ID: {0})" .format (view_id ))
123+
124+ @api (version = "3.21" )
125+ def download (self , view_item : CustomViewItem , file : PathOrFileW ) -> PathOrFileW :
126+ url = f"{ self .expurl } /{ view_item .id } /content"
127+ server_response = self .get_request (url )
128+ if isinstance (file , io_types_w ):
129+ file .write (server_response .content )
130+ return file
131+
132+ with open (file , "wb" ) as f :
133+ f .write (server_response .content )
134+
135+ return file
136+
137+ @api (version = "3.21" )
138+ def publish (self , view_item : CustomViewItem , file : PathOrFileR ) -> Optional [CustomViewItem ]:
139+ url = self .expurl
140+ if isinstance (file , io_types_r ):
141+ size = get_file_object_size (file )
142+ elif isinstance (file , (str , Path )) and (p := Path (file )).is_file ():
143+ size = p .stat ().st_size
144+ else :
145+ raise ValueError ("File path or file object required for publishing custom view." )
146+
147+ if size >= FILESIZE_LIMIT_MB * BYTES_PER_MB :
148+ upload_session_id = self .parent_srv .fileuploads .upload (file )
149+ url = f"{ url } ?uploadSessionId={ upload_session_id } "
150+ xml_request , content_type = RequestFactory .CustomView .publish_req_chunked (view_item )
151+ else :
152+ if isinstance (file , io_types_r ):
153+ file .seek (0 )
154+ contents = file .read ()
155+ if view_item .name is None :
156+ raise MissingRequiredFieldError ("Custom view item missing name." )
157+ filename = view_item .name
158+ elif isinstance (file , (str , Path )):
159+ filename = Path (file ).name
160+ contents = Path (file ).read_bytes ()
161+
162+ xml_request , content_type = RequestFactory .CustomView .publish_req (view_item , filename , contents )
163+
164+ server_response = self .post_request (url , xml_request , content_type )
165+ return CustomViewItem .from_response (server_response .content , self .parent_srv .namespace )
0 commit comments