Skip to content
Open
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
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
tableauserverclient/_version.py export-subst
tableauserverclient/bin/_version.py export-subst
11 changes: 7 additions & 4 deletions .github/workflows/publish-pypi.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
name: Publish to PyPi

# This will publish a package to TestPyPi (and real Pypi if run on master) with a version
# number generated by versioneer from the most recent tag looking like v____
# TODO: maybe move this into the package job so all release-based actions are together
# This will build a package with a version set by versioneer from the most recent tag matching v____
# It will publish to TestPyPi, and to real Pypi *if* run on master where head has a release tag
# For a live run, this should only need to be triggered by a newly published repo release.
# This can also be run manually for testing
on:
release:
types: [published]
workflow_dispatch:
push:
tags:
Expand All @@ -23,7 +26,7 @@ jobs:
- name: Build dist files
run: |
python -m pip install --upgrade pip
pip install -e .[test] build
python -m pip install -e .[test] build
python -m build
git describe --tag --dirty --always

Expand Down
3 changes: 0 additions & 3 deletions MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ include CONTRIBUTORS.md
include LICENSE
include LICENSE.versioneer
include README.md
include tableauserverclient/_version.py
include versioneer.py
recursive-include docs *.md
recursive-include samples *.py
recursive-include samples *.txt
Expand All @@ -18,5 +16,4 @@ recursive-include test *.png
recursive-include test *.py
recursive-include test *.xml
recursive-include test *.tde
global-include *.pyi
global-include *.typed
11 changes: 0 additions & 11 deletions publish.sh

This file was deleted.

13 changes: 10 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[build-system]
requires = ["setuptools>=75.0", "versioneer[toml]==0.29", "wheel"]
requires = ["setuptools>=77.0", "versioneer[toml]==0.29", "wheel"]
build-backend = "setuptools.build_meta"

[project]
Expand All @@ -8,7 +8,7 @@ name="tableauserverclient"
dynamic = ["version"]
description='A Python module for working with the Tableau Server REST API.'
authors = [{name="Tableau", email="github@tableau.com"}]
license = {file = "LICENSE"}
license-files = ["LICENSE"]
readme = "README.md"

dependencies = [
Expand All @@ -34,6 +34,13 @@ repository = "https://github.com/tableau/server-client-python"
[project.optional-dependencies]
test = ["black==24.10", "build", "mypy==1.4", "pytest>=7.0", "pytest-cov", "pytest-subtests",
"pytest-xdist", "requests-mock>=1.0,<2.0", "types-requests>=2.32.4.20250913"]

[tool.setuptools.packages.find]
where = ["tableauserverclient", "tableauserverclient.helpers", "tableauserverclient.models", "tableauserverclient.server", "tableauserverclient.server.endpoint"]

[tool.setuptools.dynamic]
version = {attr = "versioneer.get_version"}

[tool.black]
line-length = 120
target-version = ['py39', 'py310', 'py311', 'py312', 'py313']
Expand All @@ -60,5 +67,5 @@ addopts = "--junitxml=./test.junit.xml"
VCS = "git"
style = "pep440-pre"
versionfile_source = "tableauserverclient/bin/_version.py"
versionfile_build = "tableauserverclient/bin/_version.py"
versionfile_build = "_version.py"
tag_prefix = "v"
13 changes: 3 additions & 10 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,7 @@
from setuptools import setup

setup(
version=versioneer.get_version(),
cmdclass=versioneer.get_cmdclass(),
# not yet sure how to move this to pyproject.toml
packages=[
"tableauserverclient",
"tableauserverclient.helpers",
"tableauserverclient.models",
"tableauserverclient.server",
"tableauserverclient.server.endpoint",
],
# This line is required to set the version number when building the wheel
# not yet sure how to move this to pyproject.toml - it may require work in versioneer
cmdclass=versioneer.get_cmdclass()
)
4 changes: 0 additions & 4 deletions tableauserverclient/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,3 @@
"WeeklyInterval",
"WorkbookItem",
]

from .bin import _version

__version__ = _version.get_versions()["version"]
3 changes: 3 additions & 0 deletions tableauserverclient/bin/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# generated during initial setup of versioneer
from . import _version
__version__ = _version.get_versions()['version']
5 changes: 2 additions & 3 deletions tableauserverclient/bin/_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,13 @@ class VersioneerConfig:

def get_config() -> VersioneerConfig:
"""Create, populate and return the VersioneerConfig() object."""
# these strings are filled in when 'setup.py versioneer' creates
# _version.py
# these strings are filled in from pyproject.toml at file generation time
cfg = VersioneerConfig()
cfg.VCS = "git"
cfg.style = "pep440-pre"
cfg.tag_prefix = "v"
cfg.parentdir_prefix = "None"
cfg.versionfile_source = "tableauserverclient/_version.py"
cfg.versionfile_source = "tableauserverclient/bin/_version.py"
cfg.verbose = False
return cfg

Expand Down
Empty file added test/http/__init__.py
Empty file.
Empty file added test/models/__init__.py
Empty file.
59 changes: 19 additions & 40 deletions test/models/_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,48 +10,27 @@
)


def get_defined_models():
# nothing clever here: list was manually copied from tsc/models/__init__.py
return [
BackgroundJobItem,
ConnectionItem,
DataAccelerationReportItem,
DataAlertItem,
DatasourceItem,
FlowItem,
GroupItem,
JobItem,
MetricItem,
PermissionsRule,
ProjectItem,
RevisionItem,
ScheduleItem,
SubscriptionItem,
Credentials,
JWTAuth,
TableauAuth,
PersonalAccessTokenAuth,
ServerInfoItem,
SiteItem,
TaskItem,
UserItem,
ViewItem,
WebhookItem,
WorkbookItem,
PaginationItem,
Permission.Mode,
Permission.Capability,
DailyInterval,
WeeklyInterval,
MonthlyInterval,
HourlyInterval,
TableItem,
Target,
]


def get_unimplemented_models():
return [
# these items should have repr , please fix
CollectionItem,
DQWItem,
ExtensionsServer,
ExtensionsSiteSettings,
FileuploadItem,
FlowRunItem,
LinkedTaskFlowRunItem,
LinkedTaskItem,
LinkedTaskStepItem,
SafeExtension,
# these should be implemented together for consistency
CSVRequestOptions,
ExcelRequestOptions,
ImageRequestOptions,
PDFRequestOptions,
PPTXRequestOptions,
RequestOptions,
# these don't need it
FavoriteItem, # no repr because there is no state
Resource, # list of type names
TableauItem, # should be an interface
Expand Down
44 changes: 23 additions & 21 deletions test/models/test_repr.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,30 @@
import inspect
from typing import Any

import _models # type: ignore # did not set types for this
from test.models._models import get_unimplemented_models
import tableauserverclient as TSC

import pytest


# ensure that all models that don't need parameters can be instantiated
# todo....
def instantiate_class(name: str, obj: Any):
def is_concrete(obj: Any):
return inspect.isclass(obj) and not inspect.isabstract(obj)


@pytest.mark.parametrize("class_name, obj", inspect.getmembers(TSC, is_concrete))
def test_by_reflection(class_name, obj):
instance = try_instantiate_class(class_name, obj)
if instance:
class_type = type(instance)
if class_type in get_unimplemented_models():
print(f"Class '{class_name}' has no repr defined, skipping test")
return
else:
assert type(instance.__repr__).__name__ == "method"
print(instance.__repr__.__name__)


# Instantiate a class if it doesn't require any parameters
def try_instantiate_class(name: str, obj: Any) -> Any | None:
# Get the constructor (init) of the class
constructor = getattr(obj, "__init__", None)
if constructor:
Expand All @@ -22,25 +37,12 @@ def instantiate_class(name: str, obj: Any):
print(f"Class '{name}' requires the following parameters for instantiation:")
for param in required_parameters:
print(f"- {param.name}")
return None
else:
print(f"Class '{name}' does not require any parameters for instantiation.")
# Instantiate the class
instance = obj()
print(f"Instantiated: {name} -> {instance}")
return instance
else:
print(f"Class '{name}' does not have a constructor (__init__ method).")


def is_concrete(obj: Any):
return inspect.isclass(obj) and not inspect.isabstract(obj)


@pytest.mark.parametrize("class_name, obj", inspect.getmembers(TSC, is_concrete))
def test_by_reflection(class_name, obj):
instantiate_class(class_name, obj)


@pytest.mark.parametrize("model", _models.get_defined_models())
def test_repr_is_implemented(model):
print(model.__name__, type(model.__repr__).__name__)
assert type(model.__repr__).__name__ == "function"
return None
5 changes: 5 additions & 0 deletions test/test_custom_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ def test_publish_filepath(server: TSC.Server) -> None:
cv._owner = TSC.UserItem()
cv._owner._id = "dd2239f6-ddf1-4107-981a-4cf94e415794"
cv.workbook = TSC.WorkbookItem()
assert cv.workbook is not None
cv.workbook._id = "1f951daf-4061-451a-9df1-69a8062664f2"
with requests_mock.mock() as m:
m.post(server.custom_views.expurl, status_code=201, text=GET_XML.read_text())
Expand All @@ -191,6 +192,7 @@ def test_publish_file_str(server: TSC.Server) -> None:
cv._owner = TSC.UserItem()
cv._owner._id = "dd2239f6-ddf1-4107-981a-4cf94e415794"
cv.workbook = TSC.WorkbookItem()
assert cv.workbook is not None
cv.workbook._id = "1f951daf-4061-451a-9df1-69a8062664f2"
with requests_mock.mock() as m:
m.post(server.custom_views.expurl, status_code=201, text=GET_XML.read_text())
Expand All @@ -207,6 +209,7 @@ def test_publish_file_io(server: TSC.Server) -> None:
cv._owner = TSC.UserItem()
cv._owner._id = "dd2239f6-ddf1-4107-981a-4cf94e415794"
cv.workbook = TSC.WorkbookItem()
assert cv.workbook is not None
cv.workbook._id = "1f951daf-4061-451a-9df1-69a8062664f2"
data = io.BytesIO(CUSTOM_VIEW_DOWNLOAD.read_bytes())
with requests_mock.mock() as m:
Expand All @@ -223,6 +226,7 @@ def test_publish_missing_owner_id(server: TSC.Server) -> None:
cv = TSC.CustomViewItem(name="test")
cv._owner = TSC.UserItem()
cv.workbook = TSC.WorkbookItem()
assert cv.workbook is not None
cv.workbook._id = "1f951daf-4061-451a-9df1-69a8062664f2"
with requests_mock.mock() as m:
m.post(server.custom_views.expurl, status_code=201, text=GET_XML.read_text())
Expand All @@ -246,6 +250,7 @@ def test_large_publish(server: TSC.Server):
cv._owner = TSC.UserItem()
cv._owner._id = "dd2239f6-ddf1-4107-981a-4cf94e415794"
cv.workbook = TSC.WorkbookItem()
assert cv.workbook is not None
cv.workbook._id = "1f951daf-4061-451a-9df1-69a8062664f2"
with ExitStack() as stack:
temp_dir = stack.enter_context(TemporaryDirectory())
Expand Down
Loading