pytableau 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. pytableau/__init__.py +93 -0
  2. pytableau/_compat.py +46 -0
  3. pytableau/_version.py +1 -0
  4. pytableau/cli/__init__.py +15 -0
  5. pytableau/cli/main.py +137 -0
  6. pytableau/constants.py +212 -0
  7. pytableau/core/__init__.py +38 -0
  8. pytableau/core/dashboard.py +337 -0
  9. pytableau/core/datasource.py +470 -0
  10. pytableau/core/fields.py +398 -0
  11. pytableau/core/filters.py +331 -0
  12. pytableau/core/formatting.py +7 -0
  13. pytableau/core/workbook.py +352 -0
  14. pytableau/core/worksheet.py +464 -0
  15. pytableau/data/__init__.py +23 -0
  16. pytableau/data/bridge.py +157 -0
  17. pytableau/data/extract.py +114 -0
  18. pytableau/data/types.py +81 -0
  19. pytableau/exceptions.py +144 -0
  20. pytableau/inspect/__init__.py +19 -0
  21. pytableau/inspect/catalog.py +100 -0
  22. pytableau/inspect/diff.py +7 -0
  23. pytableau/inspect/lineage.py +121 -0
  24. pytableau/inspect/report.py +119 -0
  25. pytableau/package/__init__.py +12 -0
  26. pytableau/package/assets.py +7 -0
  27. pytableau/package/manager.py +146 -0
  28. pytableau/py.typed +0 -0
  29. pytableau/server/__init__.py +13 -0
  30. pytableau/server/client.py +174 -0
  31. pytableau/server/workflows.py +67 -0
  32. pytableau/templates/__init__.py +24 -0
  33. pytableau/templates/engine.py +131 -0
  34. pytableau/templates/library/__init__.py +58 -0
  35. pytableau/templates/library/bar_chart.twb +28 -0
  36. pytableau/templates/library/heatmap.twb +29 -0
  37. pytableau/templates/library/kpi_dashboard.twb +25 -0
  38. pytableau/templates/library/line_chart.twb +28 -0
  39. pytableau/templates/library/map.twb +28 -0
  40. pytableau/templates/library/scatter_plot.twb +29 -0
  41. pytableau/templates/library/treemap.twb +29 -0
  42. pytableau/templates/mapping.py +65 -0
  43. pytableau/xml/__init__.py +7 -0
  44. pytableau/xml/differ.py +7 -0
  45. pytableau/xml/discovery/__init__.py +11 -0
  46. pytableau/xml/discovery/controlled_diff.py +43 -0
  47. pytableau/xml/discovery/corpus.py +53 -0
  48. pytableau/xml/engine.py +130 -0
  49. pytableau/xml/proxy.py +37 -0
  50. pytableau/xml/schemas/__init__.py +11 -0
  51. pytableau/xml/schemas/base.py +7 -0
  52. pytableau/xml/schemas/v2022.py +7 -0
  53. pytableau/xml/schemas/v2023.py +7 -0
  54. pytableau/xml/schemas/v2024.py +7 -0
  55. pytableau/xml/schemas/v2025.py +7 -0
  56. pytableau/xml/writer.py +7 -0
  57. pytableau-0.1.0.dist-info/METADATA +177 -0
  58. pytableau-0.1.0.dist-info/RECORD +61 -0
  59. pytableau-0.1.0.dist-info/WHEEL +4 -0
  60. pytableau-0.1.0.dist-info/entry_points.txt +2 -0
  61. pytableau-0.1.0.dist-info/licenses/LICENSE +21 -0
pytableau/__init__.py ADDED
@@ -0,0 +1,93 @@
1
+ """pytableau — The unified Python SDK for Tableau workbook engineering.
2
+
3
+ Quickstart::
4
+
5
+ from pytableau import Workbook
6
+
7
+ wb = Workbook.open("sales_dashboard.twbx")
8
+ print(wb.version)
9
+ wb.save_as("modified.twbx")
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ from pytableau._version import __version__
15
+ from pytableau.constants import (
16
+ AggregationType,
17
+ ConnectionType,
18
+ DataType,
19
+ FilterType,
20
+ MarkType,
21
+ ParameterDomainType,
22
+ Role,
23
+ SortOrder,
24
+ ValidationLevel,
25
+ )
26
+ from pytableau.core.workbook import Workbook
27
+ from pytableau.exceptions import (
28
+ AuthenticationError,
29
+ ConnectionError,
30
+ CorruptWorkbookError,
31
+ DatasourceError,
32
+ DatasourceNotFoundError,
33
+ DuplicateFieldError,
34
+ ExtractError,
35
+ FieldError,
36
+ FieldNotFoundError,
37
+ FileError,
38
+ FormulaError,
39
+ HyperError,
40
+ IncompatibleVersionError,
41
+ InvalidWorkbookError,
42
+ PackageError,
43
+ PublishError,
44
+ PyTableauError,
45
+ SchemaValidationError,
46
+ ServerError,
47
+ TemplateError,
48
+ TemplateNotFoundError,
49
+ UnmappedPlaceholderError,
50
+ ValidationIssue,
51
+ XMLError,
52
+ )
53
+
54
+ __all__ = [
55
+ # Version
56
+ "__version__",
57
+ # Top-level exceptions
58
+ "PyTableauError",
59
+ "FileError",
60
+ "InvalidWorkbookError",
61
+ "PackageError",
62
+ "CorruptWorkbookError",
63
+ "XMLError",
64
+ "SchemaValidationError",
65
+ "IncompatibleVersionError",
66
+ "FieldError",
67
+ "FieldNotFoundError",
68
+ "DuplicateFieldError",
69
+ "FormulaError",
70
+ "DatasourceError",
71
+ "DatasourceNotFoundError",
72
+ "ConnectionError",
73
+ "HyperError",
74
+ "ExtractError",
75
+ "ServerError",
76
+ "AuthenticationError",
77
+ "PublishError",
78
+ "TemplateError",
79
+ "UnmappedPlaceholderError",
80
+ "TemplateNotFoundError",
81
+ "ValidationIssue",
82
+ "Workbook",
83
+ # Enums
84
+ "MarkType",
85
+ "DataType",
86
+ "Role",
87
+ "AggregationType",
88
+ "FilterType",
89
+ "SortOrder",
90
+ "ConnectionType",
91
+ "ParameterDomainType",
92
+ "ValidationLevel",
93
+ ]
pytableau/_compat.py ADDED
@@ -0,0 +1,46 @@
1
+ """Optional dependency import helpers.
2
+
3
+ Provides clear error messages when optional extras are not installed.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import importlib
9
+ from typing import Any
10
+
11
+
12
+ class _MissingDependency:
13
+ """Placeholder that raises ImportError on any attribute access."""
14
+
15
+ def __init__(self, name: str, extra: str) -> None:
16
+ self._name = name
17
+ self._extra = extra
18
+
19
+ def __getattr__(self, attr: str) -> Any:
20
+ raise ImportError(
21
+ f"{self._name} is required for this feature. "
22
+ f"Install it with: pip install pytableau[{self._extra}]"
23
+ )
24
+
25
+ def __call__(self, *args: Any, **kwargs: Any) -> Any:
26
+ raise ImportError(
27
+ f"{self._name} is required for this feature. "
28
+ f"Install it with: pip install pytableau[{self._extra}]"
29
+ )
30
+
31
+
32
+ def import_optional(module_name: str, extra: str) -> Any:
33
+ """Import an optional dependency, returning a stub if unavailable.
34
+
35
+ Args:
36
+ module_name: Fully qualified module name (e.g. ``"tableauhyperapi"``).
37
+ extra: The pip extra that provides this dependency (e.g. ``"hyper"``).
38
+
39
+ Returns:
40
+ The imported module, or a :class:`_MissingDependency` stub that raises
41
+ a helpful :class:`ImportError` on first use.
42
+ """
43
+ try:
44
+ return importlib.import_module(module_name)
45
+ except ImportError:
46
+ return _MissingDependency(module_name, extra)
pytableau/_version.py ADDED
@@ -0,0 +1 @@
1
+ __version__ = "0.1.0"
@@ -0,0 +1,15 @@
1
+ """Optional CLI interface for pytableau.
2
+
3
+ Provides the ``pytableau`` command-line tool.
4
+
5
+ Usage::
6
+
7
+ pytableau inspect workbook.twbx
8
+ pytableau diff before.twb after.twb
9
+ pytableau swap workbook.twb --server prod-db.corp.com --db analytics_prod
10
+
11
+ .. note::
12
+ Full implementation is tracked in Phase 1 of the development plan.
13
+ """
14
+
15
+ from __future__ import annotations
pytableau/cli/main.py ADDED
@@ -0,0 +1,137 @@
1
+ """CLI entry point: ``pytableau inspect``, ``pytableau diff``, ``pytableau swap``.
2
+
3
+ The CLI is built with `click <https://click.palletsprojects.com/>`_.
4
+
5
+ .. note::
6
+ Full implementation is tracked in Phase 1 of the development plan.
7
+ Phase 0 ships a minimal skeleton so that the ``pytableau`` console
8
+ script entry point is importable.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import json
14
+
15
+ try:
16
+ import click
17
+ except ImportError as exc:
18
+ raise ImportError(
19
+ "The pytableau CLI requires 'click'. "
20
+ "Install it with: pip install click"
21
+ ) from exc
22
+
23
+ from pytableau.core.workbook import Workbook
24
+
25
+
26
+ @click.group()
27
+ @click.version_option(package_name="pytableau")
28
+ def app() -> None:
29
+ """pytableau — The unified Python SDK for Tableau workbook engineering."""
30
+
31
+
32
+ @app.command()
33
+ @click.argument("workbook", type=click.Path(exists=True))
34
+ @click.option("--format", "fmt", default="table", type=click.Choice(["table", "json"]))
35
+ def inspect(workbook: str, fmt: str) -> None:
36
+ """Inspect a Tableau workbook and print its contents."""
37
+ wb = Workbook.open(workbook)
38
+ payload = {
39
+ "path": str(workbook),
40
+ "version": wb.version,
41
+ "source_platform": wb.source_platform,
42
+ "datasource_count": len(wb.datasources),
43
+ "datasources": wb.datasources.names,
44
+ "worksheet_count": len(wb.worksheets),
45
+ "worksheets": wb.worksheets.names,
46
+ "dashboard_count": len(wb.dashboards),
47
+ "dashboards": wb.dashboards.names,
48
+ }
49
+
50
+ if fmt == "json":
51
+ click.echo(json.dumps(payload, indent=2, sort_keys=True))
52
+ return
53
+
54
+ click.echo(f"Workbook: {workbook}")
55
+ click.echo(f"Version: {wb.version}")
56
+ if wb.source_platform:
57
+ click.echo(f"Source platform: {wb.source_platform}")
58
+ click.echo(f"Datasources ({payload['datasource_count']}):")
59
+ for name in payload["datasources"]:
60
+ click.echo(f" - {name}")
61
+ click.echo(f"Worksheets ({payload['worksheet_count']}):")
62
+ for name in payload["worksheets"]:
63
+ click.echo(f" - {name}")
64
+ click.echo(f"Dashboards ({payload['dashboard_count']}):")
65
+ for name in payload["dashboards"]:
66
+ click.echo(f" - {name}")
67
+
68
+
69
+ @app.command()
70
+ @click.argument("before", type=click.Path(exists=True))
71
+ @click.argument("after", type=click.Path(exists=True))
72
+ def diff(before: str, after: str) -> None:
73
+ """Show semantic differences between two Tableau workbooks."""
74
+ before_wb = Workbook.open(before)
75
+ after_wb = Workbook.open(after)
76
+
77
+ before_xml = before_wb.to_xml_string().splitlines()
78
+ after_xml = after_wb.to_xml_string().splitlines()
79
+
80
+ if before_xml == after_xml:
81
+ click.echo("No differences detected.")
82
+ return
83
+
84
+ before_set = {line for line in before_xml}
85
+ after_set = {line for line in after_xml}
86
+ added = sorted(after_set - before_set)
87
+ removed = sorted(before_set - after_set)
88
+
89
+ click.echo(f"Added lines: {len(added)}")
90
+ for item in added[:40]:
91
+ click.echo(f" + {item}")
92
+ if len(added) > 40:
93
+ click.echo(f" ... ({len(added) - 40} more)")
94
+
95
+ click.echo(f"Removed lines: {len(removed)}")
96
+ for item in removed[:40]:
97
+ click.echo(f" - {item}")
98
+ if len(removed) > 40:
99
+ click.echo(f" ... ({len(removed) - 40} more)")
100
+
101
+
102
+ @app.command()
103
+ @click.argument("workbook", type=click.Path(exists=True))
104
+ @click.option("--server", required=True, help="New database server hostname")
105
+ @click.option("--db", required=False, help="New database name")
106
+ @click.option("--username", required=False, help="New database username")
107
+ @click.option("--output", "-o", required=False, help="Output path (default: overwrite)")
108
+ def swap(
109
+ workbook: str,
110
+ server: str,
111
+ db: str | None,
112
+ username: str | None,
113
+ output: str | None,
114
+ ) -> None:
115
+ """Swap connection properties in a Tableau workbook."""
116
+ wb = Workbook.open(workbook)
117
+ updated = 0
118
+
119
+ for conn in wb.xml_tree.findall(".//connection"):
120
+ if server:
121
+ conn.set("server", server)
122
+ updated += 1
123
+ if db:
124
+ if "dbname" in conn.attrib:
125
+ conn.set("dbname", db)
126
+ elif "dbName" in conn.attrib:
127
+ conn.set("dbName", db)
128
+ else:
129
+ conn.set("dbname", db)
130
+ updated += 1
131
+ if username:
132
+ conn.set("username", username)
133
+ updated += 1
134
+
135
+ destination = output or workbook
136
+ wb.save_as(destination)
137
+ click.echo(f"Updated {updated} connection attributes.")
pytableau/constants.py ADDED
@@ -0,0 +1,212 @@
1
+ """Constants and enumerations for pytableau.
2
+
3
+ All Tableau-level concepts (mark types, data types, roles, etc.) are
4
+ represented as ``str`` enums so that values compare equal to the raw
5
+ XML attribute strings used by Tableau.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from enum import Enum
11
+
12
+
13
+ class MarkType(str, Enum):
14
+ """Viz mark types as they appear in Tableau XML."""
15
+
16
+ AUTOMATIC = "automatic"
17
+ BAR = "bar"
18
+ LINE = "line"
19
+ AREA = "area"
20
+ CIRCLE = "circle"
21
+ SQUARE = "square"
22
+ SHAPE = "shape" # custom shapes palette
23
+ TEXT = "text"
24
+ MAP = "map"
25
+ PIE = "pie"
26
+ GANTT = "gantt_rounded"
27
+ GANTT_BAR = "gantt_rounded" # alias — same XML value
28
+ POLYGON = "polygon"
29
+ DENSITY = "density"
30
+
31
+
32
+ class DataType(str, Enum):
33
+ """Field data types as they appear in Tableau XML."""
34
+
35
+ STRING = "string"
36
+ INTEGER = "integer"
37
+ REAL = "real"
38
+ BOOLEAN = "boolean"
39
+ DATE = "date"
40
+ DATETIME = "datetime"
41
+
42
+
43
+ class Role(str, Enum):
44
+ """Semantic role of a field."""
45
+
46
+ DIMENSION = "dimension"
47
+ MEASURE = "measure"
48
+
49
+
50
+ class AggregationType(str, Enum):
51
+ """Default aggregation types for measure fields."""
52
+
53
+ SUM = "Sum"
54
+ AVG = "Avg"
55
+ COUNT = "Count"
56
+ COUNTD = "CountD"
57
+ MIN = "Min"
58
+ MAX = "Max"
59
+ MEDIAN = "Median"
60
+ ATTR = "Attr"
61
+ STDEV = "Stdev"
62
+ VAR = "Var"
63
+ YEAR = "Year"
64
+ QUARTER = "Quarter"
65
+ MONTH = "Month"
66
+ WEEK = "Week"
67
+ DAY = "Day"
68
+ NONE = "None"
69
+
70
+
71
+ class FilterType(str, Enum):
72
+ """Filter types as they appear in Tableau XML."""
73
+
74
+ CATEGORICAL = "categorical"
75
+ QUANTITATIVE = "quantitative"
76
+ RELATIVE_DATE = "relative-date"
77
+ RANGE = "range"
78
+ TOP = "top"
79
+ WILDCARD = "wildcard"
80
+ ALL = "all"
81
+
82
+
83
+ class SortOrder(str, Enum):
84
+ """Sort direction options."""
85
+
86
+ ASCENDING = "asc"
87
+ DESCENDING = "desc"
88
+ NONE = "none"
89
+
90
+
91
+ class SortType(str, Enum):
92
+ """Sort method options."""
93
+
94
+ ALPHABETIC = "alphabetic"
95
+ DATA_SOURCE = "data-source-order"
96
+ FIELD = "field"
97
+ MANUAL = "manual"
98
+ NONE = "none"
99
+
100
+
101
+ class ConnectionType(str, Enum):
102
+ """Common Tableau connection class names (``class`` XML attribute)."""
103
+
104
+ HYPER = "hyper"
105
+ TEXTSCAN = "textscan" # CSV / text files
106
+ EXCEL = "excel-direct"
107
+ SQLSERVER = "sqlserver"
108
+ MSSQL = "sqlserver" # alias — same XML class value as SQLSERVER
109
+ POSTGRES = "postgres"
110
+ MYSQL = "mysql"
111
+ ORACLE = "oracle"
112
+ REDSHIFT = "redshift"
113
+ SNOWFLAKE = "snowflake"
114
+ BIGQUERY = "bigquery"
115
+ DATABRICKS = "spark"
116
+ ATHENA = "athena"
117
+ TABLEAU_SERVER = "tableau-server" # published data source on Tableau Server/Cloud
118
+
119
+
120
+ class DashboardSizeType(str, Enum):
121
+ """Dashboard size constraint modes."""
122
+
123
+ FIXED = "fixed"
124
+ AUTOMATIC = "automatic"
125
+ RANGE = "range"
126
+
127
+
128
+ class ZoneType(str, Enum):
129
+ """Dashboard zone layout types."""
130
+
131
+ LAYOUT_BASIC = "layout-basic"
132
+ LAYOUT_FLOW = "layout-flow"
133
+ WORKSHEET = "worksheet"
134
+ TEXT = "text"
135
+ IMAGE = "image"
136
+ WEB = "web"
137
+ BLANK = "blank"
138
+ TITLE = "title"
139
+ LEGEND = "legend"
140
+ FILTER = "filter"
141
+ PARAMETER = "parameter"
142
+ EXTENSION = "extension"
143
+
144
+
145
+ class ShelfType(str, Enum):
146
+ """Worksheet shelf identifiers."""
147
+
148
+ ROWS = "rows"
149
+ COLUMNS = "columns"
150
+ COLOR = "color"
151
+ SIZE = "size"
152
+ LABEL = "label"
153
+ DETAIL = "detail"
154
+ TOOLTIP = "tooltip"
155
+ PATH = "path"
156
+ SHAPE = "shape"
157
+ ANGLE = "angle"
158
+
159
+
160
+ class ActionType(str, Enum):
161
+ """Dashboard action types."""
162
+
163
+ FILTER = "filter"
164
+ HIGHLIGHT = "highlight"
165
+ URL = "url"
166
+ GOTO_SHEET = "goto-sheet"
167
+ CHANGE_PARAMETER = "change-parameter"
168
+ CHANGE_SET_VALUES = "change-set-values"
169
+
170
+
171
+ class ParameterDomainType(str, Enum):
172
+ """Parameter allowable values type."""
173
+
174
+ ALL = "all"
175
+ LIST = "list"
176
+ RANGE = "range"
177
+
178
+
179
+ class ValidationLevel(str, Enum):
180
+ """Severity levels for validation issues."""
181
+
182
+ ERROR = "error"
183
+ WARNING = "warning"
184
+ INFO = "info"
185
+
186
+
187
+ # ---------------------------------------------------------------------------
188
+ # Tableau version mapping: version string → XML source-build attribute value
189
+ # ---------------------------------------------------------------------------
190
+
191
+ #: Maps human-readable Tableau version strings to the ``source-build``
192
+ #: attribute value written in workbook XML headers.
193
+ TABLEAU_VERSION_MAP: dict[str, str] = {
194
+ "2022.1": "20221.22.0418.1333",
195
+ "2022.2": "20222.22.0719.1543",
196
+ "2022.3": "20223.22.1007.0912",
197
+ "2022.4": "20224.23.0112.0803",
198
+ "2023.1": "20231.23.0309.0931",
199
+ "2023.2": "20232.23.0609.0906",
200
+ "2023.3": "20233.23.0921.0921",
201
+ "2023.4": "20234.24.0111.0824",
202
+ "2024.1": "20241.24.0312.0830",
203
+ "2024.2": "20242.24.0613.0835",
204
+ "2024.3": "20243.24.0912.0921",
205
+ "2025.1": "20251.25.0314.0930",
206
+ }
207
+
208
+ #: The minimum Tableau version supported by pytableau.
209
+ MINIMUM_TABLEAU_VERSION = "2022.1"
210
+
211
+ #: The default Tableau version used when creating new workbooks.
212
+ DEFAULT_TABLEAU_VERSION = "2024.1"
@@ -0,0 +1,38 @@
1
+ """Core object model for pytableau.
2
+
3
+ Provides the high-level Python objects that map to Tableau workbook
4
+ concepts: Workbook, Datasource, Worksheet, Dashboard, and field-like objects.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from .dashboard import Dashboard, DashboardCollection
10
+ from .datasource import Datasource, DatasourceCollection
11
+ from .fields import (
12
+ CalcFieldCollection,
13
+ CalculatedField,
14
+ Field,
15
+ FieldCollection,
16
+ FieldReference,
17
+ Parameter,
18
+ )
19
+ from .workbook import Workbook
20
+ from .worksheet import MarkCard, Shelf, Worksheet, WorksheetCollection
21
+
22
+ __all__ = [
23
+ "Workbook",
24
+ "Datasource",
25
+ "DatasourceCollection",
26
+ "Dashboard",
27
+ "DashboardCollection",
28
+ "Worksheet",
29
+ "WorksheetCollection",
30
+ "Field",
31
+ "FieldCollection",
32
+ "CalcFieldCollection",
33
+ "CalculatedField",
34
+ "FieldReference",
35
+ "Parameter",
36
+ "MarkCard",
37
+ "Shelf",
38
+ ]