Metadata-Version: 2.4
Name: tfl-decompiler
Version: 1.0.2
Summary: Convert Tableau Prep Flow files to Python/pandas scripts
Home-page: https://github.com/your-repo/tfl-decompiler
Author: TFL Decompiler
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Database
Classifier: Topic :: Scientific/Engineering :: Information Analysis
Requires-Python: >=3.9
Description-Content-Type: text/markdown
Requires-Dist: pandas>=2.0.0
Requires-Dist: numpy>=1.24.0
Requires-Dist: sqlalchemy>=2.0.0
Requires-Dist: openpyxl>=3.1.0
Provides-Extra: postgresql
Requires-Dist: psycopg2-binary>=2.9.0; extra == "postgresql"
Provides-Extra: mysql
Requires-Dist: mysql-connector-python>=8.0.0; extra == "mysql"
Provides-Extra: saphana
Requires-Dist: hdbcli>=2.10.0; extra == "saphana"
Provides-Extra: all
Requires-Dist: psycopg2-binary>=2.9.0; extra == "all"
Requires-Dist: mysql-connector-python>=8.0.0; extra == "all"
Requires-Dist: hdbcli>=2.10.0; extra == "all"
Dynamic: author
Dynamic: classifier
Dynamic: description
Dynamic: description-content-type
Dynamic: home-page
Dynamic: provides-extra
Dynamic: requires-dist
Dynamic: requires-python
Dynamic: summary

# TFL Decompiler

**Convert Tableau Prep Flow files (`.tfl`) to Python/pandas scripts.**

[![PyPI version](https://badge.fury.io/py/tfl-decompiler.svg)](https://pypi.org/project/tfl-decompiler/)
[![Python](https://img.shields.io/pypi/pyversions/tfl-decompiler.svg)](https://pypi.org/project/tfl-decompiler/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

Say goodbye to Tableau Prep's proprietary flows. Transform your data transformations into portable, version-controlled, scalable Python code.

---

## Table of Contents

- [Installation](#installation)
- [Quick Start](#quick-start)
- [Usage](#usage)
  - [Command Line](#command-line)
  - [Python API](#python-api)
- [Example Output](#example-output)
- [Supported Transformations](#supported-transformations)
- [Architecture](#architecture)
- [Why Replace Tableau Prep?](#why-replace-tableau-prep)
- [Contributing](#contributing)
- [License](#license)

---

## Installation

```bash
pip install tfl-decompiler
```

**With optional database connectors:**

```bash
# PostgreSQL
pip install "tfl-decompiler[postgresql]"

# MySQL
pip install "tfl-decompiler[mysql]"

# SAP HANA
pip install "tfl-decompiler[saphana]"

# All database connectors
pip install "tfl-decompiler[all]"
```

**Requirements:** Python 3.9+

---

## Quick Start

```bash
tfl-decompile my_flow.tfl -o my_flow.py
python my_flow.py
```

---

## Usage

### Command Line

After installation, the `tfl-decompile` command is available on your PATH.

```bash
# Convert a flow to Python (save to file)
tfl-decompile my_flow.tfl -o my_flow.py

# Print generated code to stdout
tfl-decompile my_flow.tfl

# Analyze flow structure without generating code
tfl-decompile my_flow.tfl --analyze

# Batch convert all flows in a directory
for f in *.tfl; do tfl-decompile "$f" -o "${f%.tfl}.py"; done
```

You can also run it as a module:

```bash
python -m tfl_decompiler my_flow.tfl -o my_flow.py
```

### Python API

```python
from tfl_decompiler import decompile

# Generate Python code string
code = decompile("my_flow.tfl")
print(code)

# Generate and save to file
decompile("my_flow.tfl", output_path="my_flow.py")

# Inspect the flow structure
from tfl_decompiler import TFLExtractor

extractor = TFLExtractor("my_flow.tfl")
content = extractor.extract()
summary = extractor.get_flow_summary(content)
print(summary)

# Fine-grained control
from tfl_decompiler import FlowParser, CodeGenerator

parser = FlowParser(content)
graph = parser.build_graph()
generator = CodeGenerator(graph)
code = generator.generate()
```

---

## Example Output

Given a Tableau Prep flow that reads a CSV, renames columns, filters rows, aggregates, and writes output:

```python
#!/usr/bin/env python3
"""
Auto-generated from: sales_flow.tfl
Generated by: TFL Decompiler
"""

import pandas as pd
import numpy as np
import os
from pathlib import Path


def main(input_sales: str = "data/sales.csv", output_results: str = "output/results.csv"):
    """Execute the data transformation flow."""

    # Step 1: Load data from CSV file
    df_input_sales = pd.read_csv(input_sales)

    # Step 2: Clean step (3 operations): rename, changetype, filter
    df_clean_transform = df_input_sales.copy()
    df_clean_transform = df_clean_transform.rename(columns={"old_name": "new_name"})
    df_clean_transform["amount"] = df_clean_transform["amount"].astype(float)
    df_clean_transform = df_clean_transform[df_clean_transform["status"] == "active"]

    # Step 3: Aggregate by [product_category]
    df_agg_summary = df_clean_transform.groupby("product_category").agg(
        {"amount": "sum", "quantity": "mean"}
    ).reset_index()

    # Step 4: Output to CSV
    os.makedirs(Path(output_results).parent, exist_ok=True)
    df_agg_summary.to_csv(output_results, index=False)

    return df_agg_summary


if __name__ == "__main__":
    import argparse

    parser = argparse.ArgumentParser(description="Decompiled Tableau Prep flow")
    parser.add_argument("--input_sales", default="data/sales.csv")
    parser.add_argument("--output_results", default="output/results.csv")
    args = parser.parse_args()
    result = main(input_sales=args.input_sales, output_results=args.output_results)
    print(f"Processing complete. Output shape: {result.shape}")
```

---

## Supported Transformations

### Node Types

| Node | Description |
|------|-------------|
| Input | CSV, Excel, JSON, PostgreSQL, MySQL, SAP HANA |
| Clean / Transform | Rename, filter, calculate, type conversion, and more |
| Pivot | Columns-to-rows (melt) and rows-to-columns (pivot_table) |
| Aggregate | groupby with all common aggregations |
| Join | Inner, left, right, full outer |
| Union | Vertical concatenation |
| Filter | Boolean conditions |
| Output | CSV, Excel, databases |

### Input Sources

| Tableau | Pandas |
|---------|--------|
| CSV File | `pd.read_csv()` |
| Excel File | `pd.read_excel()` |
| JSON File | `pd.read_json()` |
| PostgreSQL | `pd.read_sql()` with psycopg2 |
| MySQL | `pd.read_sql()` with mysql-connector |
| SAP HANA | `pd.read_sql()` with hdbcli |

### Clean Operations

| Tableau | Pandas |
|---------|--------|
| Rename | `df.rename(columns={})` |
| Remove Field | `df.drop(columns=[])` |
| Change Type | `df.astype()` / `pd.to_datetime()` |
| Filter | `df[condition]` |
| Replace Values | `df.replace()` |
| Split Field | `df[col].str.split()` |
| Merge Fields | String concatenation |
| Fill Nulls | `df.fillna()` |
| Remove Nulls | `df.dropna()` |
| Calculated Field | New column assignment |
| Trim | `df[col].str.strip()` |
| Case Conversion | `str.upper()` / `str.lower()` |

### Pivot Operations

| Tableau | Pandas |
|---------|--------|
| Columns to Rows | `pd.melt()` |
| Rows to Columns | `df.pivot_table()` |

### Aggregations

| Tableau | Pandas |
|---------|--------|
| SUM | `sum` |
| AVG | `mean` |
| COUNT | `count` |
| COUNTD | `nunique` |
| MIN | `min` |
| MAX | `max` |
| MEDIAN | `median` |
| STDEV | `std` |

### Joins

| Tableau | Pandas |
|---------|--------|
| Inner | `pd.merge(how='inner')` |
| Left | `pd.merge(how='left')` |
| Right | `pd.merge(how='right')` |
| Full Outer | `pd.merge(how='outer')` |

### Tableau Expression Syntax

| Tableau | Pandas |
|---------|--------|
| `[Field Name]` | `df['Field Name']` |
| `IF...THEN...ELSE...END` | `np.where()` |
| `CONTAINS([Field], 'x')` | `df['Field'].str.contains('x')` |
| `LEFT([Field], n)` | `df['Field'].str[:n]` |
| `UPPER([Field])` | `df['Field'].str.upper()` |
| `ZN([Field])` | `df['Field'].fillna(0)` |
| `IFNULL([Field], val)` | `df['Field'].fillna(val)` |
| `YEAR([Date])` | `df['Date'].dt.year` |

---

## Architecture

```
tfl_decompiler/
├── __init__.py           # Public API exports
├── __main__.py           # python -m tfl_decompiler entry point
├── main.py               # CLI entry point (tfl-decompile command)
├── extractor.py          # .tfl ZIP extraction and JSON parsing
├── flow_parser.py        # Flow graph construction + topological sort
├── node_registry.py      # Node type → handler mapping
├── code_generator.py     # Python code generation
├── expression_parser.py  # Tableau calc → pandas expression translator
├── db_connectors.py      # Database connection helpers
├── utils.py              # Utility functions
├── nodes/
│   ├── base.py           # Abstract base node
│   ├── input_node.py     # Data source handlers
│   ├── clean_node.py     # Transform handlers
│   ├── pivot_node.py     # Pivot handlers
│   ├── aggregate_node.py # Aggregation handlers
│   ├── join_node.py      # Join handlers
│   ├── union_node.py     # Union handlers
│   ├── filter_node.py    # Filter handlers
│   └── output_node.py    # Output handlers
└── builder/
    ├── flow_builder.py   # High-level flow assembly
    └── node_builders.py  # Per-node code builders
```

---

## Why Replace Tableau Prep?

| Tableau Prep | Python / pandas |
|--------------|-----------------|
| Proprietary format | Plain text, version-controlled |
| GUI-only | Scriptable and automatable |
| License required | Free and open source |
| Limited scalability | Scale to any size |
| Black box | Full transparency |
| Desktop only | Runs anywhere |

---

## Contributing

Contributions are welcome. Areas of interest:

- Additional node types
- More Tableau functions in the expression parser
- Additional database connectors
- Test cases with real `.tfl` files

Please open an issue or pull request on GitHub.

---

## License

[MIT License](https://opensource.org/licenses/MIT)
