Skip to main content

A powerful data grid library with sorting, filtering, pagination, tree view, and export features

Project description

PyDataGrid

A powerful and flexible Python library for creating interactive data grids with sorting, filtering, pagination, and export capabilities. Perfect for displaying tabular data from Python APIs in a beautiful web interface.

📚 Documentation

Features

Core Features:

  • 📊 Display data in a beautiful, responsive table
  • 🔄 Sort data by any column (ascending/descending)
  • 🔍 Filter data by column values (real-time)
  • 📄 Pagination with configurable page size
  • ☑️ Row selection (individual, all, cross-page)
  • 📥 Export selected or all filtered data to CSV
  • 👁️ Show/hide columns dynamically
  • 📱 Responsive design for all screen sizes

Advanced Features:

  • ⚡ High performance with large datasets
  • 🎨 Professional UI with hover tooltips
  • � Visual filter indicators
  • 🌐 REST API for Angular/React integration
  • 🐳 Docker-ready
  • 📦 PyPI publishable
  • 🔧 Easy integration with Flask, Django, FastAPI

Installation

From Source

git clone https://github.com/yourusername/pydatagrid.git
cd pydatagrid
pip install -e .

Using pip (once published)

pip install pydatagrid

Quick Start

Basic Usage with Flask

from pydatagrid import create_grid_app

# Your data
data = [
    {'id': 1, 'name': 'John Doe', 'email': 'john@example.com', 'age': 30, 'city': 'New York'},
    {'id': 2, 'name': 'Jane Smith', 'email': 'jane@example.com', 'age': 25, 'city': 'London'},
    {'id': 3, 'name': 'Bob Johnson', 'email': 'bob@example.com', 'age': 35, 'city': 'Paris'},
    # ... more data
]

# Create and run the grid application
app = create_grid_app(data, host='127.0.0.1', port=5000, debug=True)
app.run()

Then open your browser and navigate to http://127.0.0.1:5000/grid/

Advanced Usage with Custom Flask App

from flask import Flask
from flask_cors import CORS
from pydatagrid import create_grid_blueprint

app = Flask(__name__)
CORS(app)

# Your data source (can be a function)
def get_data():
    # Fetch data from database, API, or any source
    return [
        {'id': 1, 'name': 'Product A', 'price': 29.99, 'stock': 100},
        {'id': 2, 'name': 'Product B', 'price': 49.99, 'stock': 50},
        # ... more data
    ]

# Create and register the grid blueprint
grid_blueprint = create_grid_blueprint(
    data_source=get_data,
    name='products_grid',
    url_prefix='/products'
)
app.register_blueprint(grid_blueprint)

if __name__ == '__main__':
    app.run(debug=True)

Access the grid at http://127.0.0.1:5000/products/

Using DataGrid Class Directly

from pydatagrid import DataGrid

# Create a DataGrid instance
data = [
    {'name': 'Alice', 'score': 95, 'grade': 'A'},
    {'name': 'Bob', 'score': 87, 'grade': 'B'},
    {'name': 'Charlie', 'score': 92, 'grade': 'A'},
]

grid = DataGrid(data)

# Apply sorting
grid.setSorting('score', 'desc')

# Apply filtering
grid.setFilter('grade', 'A')

# Set pagination
grid.setPage(1).setPageSize(50)

# Get processed data
processed_data = grid.getProcessedData()
print(processed_data)

# Export to CSV
csv_data = grid.toCsv()
with open('export.csv', 'w') as f:
    f.write(csv_data)

Integration with Angular

PyDataGrid provides ag-grid style integration for Angular applications. You can use it as a component with property bindings or consume the REST API directly.

Method 1: Component Integration (ag-grid Style)

This approach mirrors ag-grid's integration pattern with property bindings and event handlers.

Step 1: Install and Setup Backend

# server.py
from pydatagrid import create_grid_app
from flask_cors import CORS

# Your data source
data = [
    {'id': 1, 'name': 'John Doe', 'email': 'john@example.com', 'age': 30, 'city': 'New York'},
    {'id': 2, 'name': 'Jane Smith', 'email': 'jane@example.com', 'age': 25, 'city': 'London'},
    {'id': 3, 'name': 'Bob Johnson', 'email': 'bob@example.com', 'age': 35, 'city': 'Paris'},
]

app = create_grid_app(data, host='0.0.0.0', port=5000)
CORS(app)  # Enable CORS for Angular
app.run()

Step 2: Create PyDataGrid Angular Component

// pydatagrid.component.ts
import { Component, Input, OnInit, OnChanges } from '@angular/core';
import { HttpClient, HttpParams } from '@angular/common/http';

export interface ColumnDef {
  field: string;
  headerName?: string;
  sortable?: boolean;
  filter?: boolean;
  width?: number;
}

export interface GridOptions {
  columnDefs: ColumnDef[];
  pagination?: boolean;
  paginationPageSize?: number;
  apiUrl?: string;
  enableSorting?: boolean;
  enableFiltering?: boolean;
  enableExport?: boolean;
}

@Component({
  selector: 'pydatagrid',
  template: `
    <div class="pydatagrid-wrapper">
      <!-- Toolbar -->
      <div class="grid-toolbar" *ngIf="gridOptions.enableExport">
        <button (click)="onExportSelected()" [disabled]="selectedRows.size === 0">
          Export Selected
        </button>
        <button (click)="onExportAll()">Export All</button>
      </div>

      <!-- Grid Table -->
      <div class="grid-container">
        <table class="pydatagrid-table">
          <thead>
            <tr>
              <th class="select-column">
                <input type="checkbox" 
                       (change)="onSelectAll($event.target.checked)"
                       [checked]="isAllSelected()">
              </th>
              <th *ngFor="let col of gridOptions.columnDefs" 
                  [style.width.px]="col.width"
                  [class.sortable]="col.sortable !== false"
                  (click)="col.sortable !== false && onSort(col.field)">
                {{ col.headerName || col.field }}
                <span *ngIf="sortColumn === col.field">
                  {{ sortDirection === 'asc' ? '▲' : '▼' }}
                </span>
              </th>
            </tr>
            <tr *ngIf="gridOptions.enableFiltering !== false">
              <th></th>
              <th *ngFor="let col of gridOptions.columnDefs">
                <input *ngIf="col.filter !== false"
                       type="text" 
                       placeholder="Filter..."
                       (input)="onFilter(col.field, $event.target.value)"
                       class="filter-input">
              </th>
            </tr>
          </thead>
          <tbody>
            <tr *ngFor="let row of rowData; let i = index"
                [class.selected]="selectedRows.has(row.id || i)">
              <td class="select-column">
                <input type="checkbox"
                       [checked]="selectedRows.has(row.id || i)"
                       (change)="onRowSelect(row.id || i, $event.target.checked)">
              </td>
              <td *ngFor="let col of gridOptions.columnDefs">
                {{ row[col.field] }}
              </td>
            </tr>
          </tbody>
        </table>
      </div>

      <!-- Pagination -->
      <div class="pagination" *ngIf="gridOptions.pagination">
        <button (click)="onPageChange(1)" [disabled]="currentPage === 1">First</button>
        <button (click)="onPageChange(currentPage - 1)" [disabled]="currentPage === 1">Previous</button>
        <span>Page {{ currentPage }} of {{ totalPages }}</span>
        <button (click)="onPageChange(currentPage + 1)" [disabled]="currentPage === totalPages">Next</button>
        <button (click)="onPageChange(totalPages)" [disabled]="currentPage === totalPages">Last</button>
      </div>
    </div>
  `,
  styles: [`
    .pydatagrid-wrapper {
      width: 100%;
      font-family: Arial, sans-serif;
    }
    .grid-toolbar {
      padding: 10px;
      background: #f5f5f5;
      border: 1px solid #ddd;
      border-bottom: none;
    }
    .grid-toolbar button {
      margin-right: 10px;
      padding: 8px 16px;
      background: #2196F3;
      color: white;
      border: none;
      border-radius: 4px;
      cursor: pointer;
    }
    .grid-toolbar button:disabled {
      background: #ccc;
      cursor: not-allowed;
    }
    .grid-container {
      overflow-x: auto;
      border: 1px solid #ddd;
    }
    .pydatagrid-table {
      width: 100%;
      border-collapse: collapse;
    }
    .pydatagrid-table th,
    .pydatagrid-table td {
      padding: 12px;
      text-align: left;
      border-bottom: 1px solid #ddd;
    }
    .pydatagrid-table th {
      background: #f8f9fa;
      font-weight: 600;
      position: relative;
    }
    .pydatagrid-table th.sortable {
      cursor: pointer;
      user-select: none;
    }
    .pydatagrid-table th.sortable:hover {
      background: #e9ecef;
    }
    .pydatagrid-table tr.selected {
      background: #e3f2fd;
    }
    .pydatagrid-table tr:hover {
      background: #f5f5f5;
    }
    .select-column {
      width: 40px;
      text-align: center !important;
    }
    .filter-input {
      width: 100%;
      padding: 6px;
      border: 1px solid #ddd;
      border-radius: 4px;
      font-size: 13px;
    }
    .pagination {
      padding: 10px;
      text-align: center;
      background: #f5f5f5;
      border: 1px solid #ddd;
      border-top: none;
    }
    .pagination button {
      margin: 0 5px;
      padding: 6px 12px;
      background: white;
      border: 1px solid #ddd;
      border-radius: 4px;
      cursor: pointer;
    }
    .pagination button:disabled {
      opacity: 0.5;
      cursor: not-allowed;
    }
  `]
})
export class PyDataGridComponent implements OnInit, OnChanges {
  @Input() gridOptions!: GridOptions;
  @Input() rowData: any[] = [];
  
  selectedRows = new Set<any>();
  currentPage = 1;
  totalPages = 1;
  sortColumn: string = '';
  sortDirection: 'asc' | 'desc' = 'asc';
  filters: { [key: string]: string } = {};
  
  private apiUrl: string;

  constructor(private http: HttpClient) {
    this.apiUrl = 'http://localhost:5000/grid/api';
  }

  ngOnInit() {
    if (this.gridOptions.apiUrl) {
      this.apiUrl = this.gridOptions.apiUrl;
    }
    this.loadData();
  }

  ngOnChanges() {
    if (this.rowData && this.rowData.length > 0) {
      // If rowData is provided directly, use it (no API call)
      return;
    }
    this.loadData();
  }

  loadData() {
    let params = new HttpParams()
      .set('page', this.currentPage.toString())
      .set('page_size', (this.gridOptions.paginationPageSize || 100).toString())
      .set('sort_column', this.sortColumn)
      .set('sort_direction', this.sortDirection);

    Object.entries(this.filters).forEach(([key, value]) => {
      if (value) {
        params = params.set(`filter_${key}`, value);
      }
    });

    this.http.get<any>(`${this.apiUrl}/data`, { params }).subscribe({
      next: (response) => {
        this.rowData = response.data;
        this.totalPages = response.totalPages;
      },
      error: (err) => console.error('Error loading data:', err)
    });
  }

  onSort(field: string) {
    if (this.sortColumn === field) {
      this.sortDirection = this.sortDirection === 'asc' ? 'desc' : 'asc';
    } else {
      this.sortColumn = field;
      this.sortDirection = 'asc';
    }
    this.loadData();
  }

  onFilter(field: string, value: string) {
    if (value) {
      this.filters[field] = value;
    } else {
      delete this.filters[field];
    }
    this.currentPage = 1;
    this.loadData();
  }

  onPageChange(page: number) {
    this.currentPage = page;
    this.loadData();
  }

  onRowSelect(id: any, checked: boolean) {
    if (checked) {
      this.selectedRows.add(id);
    } else {
      this.selectedRows.delete(id);
    }
  }

  onSelectAll(checked: boolean) {
    if (checked) {
      this.rowData.forEach(row => this.selectedRows.add(row.id || row));
    } else {
      this.selectedRows.clear();
    }
  }

  isAllSelected(): boolean {
    return this.rowData.length > 0 && 
           this.rowData.every(row => this.selectedRows.has(row.id || row));
  }

  onExportSelected() {
    const url = `${this.apiUrl}/export?export_type=selected&selected_rows=${JSON.stringify(Array.from(this.selectedRows))}`;
    window.open(url, '_blank');
  }

  onExportAll() {
    const url = `${this.apiUrl}/export?export_type=all`;
    window.open(url, '_blank');
  }
}

Step 3: Register Component in Module

// app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { HttpClientModule } from '@angular/common/http';
import { PyDataGridComponent } from './pydatagrid.component';
import { AppComponent } from './app.component';

@NgModule({
  declarations: [
    AppComponent,
    PyDataGridComponent
  ],
  imports: [
    BrowserModule,
    HttpClientModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Step 4: Use Like ag-grid

// app.component.ts
import { Component } from '@angular/core';
import { GridOptions, ColumnDef } from './pydatagrid.component';

@Component({
  selector: 'app-root',
  template: `
    <div style="padding: 20px;">
      <h1>My Data Grid (ag-grid style)</h1>
      
      <!-- Use PyDataGrid like ag-grid -->
      <pydatagrid 
        [gridOptions]="gridOptions"
        style="height: 600px; width: 100%;">
      </pydatagrid>
    </div>
  `
})
export class AppComponent {
  // Define grid options (similar to ag-grid)
  gridOptions: GridOptions = {
    columnDefs: [
      { field: 'id', headerName: 'ID', width: 80 },
      { field: 'name', headerName: 'Full Name', sortable: true },
      { field: 'email', headerName: 'Email Address', sortable: true, filter: true },
      { field: 'age', headerName: 'Age', width: 100, sortable: true },
      { field: 'city', headerName: 'City', filter: true }
    ],
    pagination: true,
    paginationPageSize: 50,
    enableSorting: true,
    enableFiltering: true,
    enableExport: true,
    apiUrl: 'http://localhost:5000/grid/api'  // Optional: customize API URL
  };
}

Alternative: With Static Data

// app.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  template: `
    <pydatagrid 
      [gridOptions]="gridOptions"
      [rowData]="rowData">
    </pydatagrid>
  `
})
export class AppComponent {
  gridOptions: GridOptions = {
    columnDefs: [
      { field: 'name', headerName: 'Name' },
      { field: 'age', headerName: 'Age' },
      { field: 'city', headerName: 'City' }
    ],
    pagination: true,
    paginationPageSize: 10
  };

  // Provide data directly (no API calls)
  rowData = [
    { name: 'John', age: 30, city: 'New York' },
    { name: 'Jane', age: 25, city: 'London' },
    { name: 'Bob', age: 35, city: 'Paris' }
  ];
}

Method 2: iframe Embedding (Quickest)

For rapid prototyping or when you want the full PyDataGrid UI:

// app.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  template: `
    <div class="container">
      <h1>My Data Grid</h1>
      <iframe 
        src="http://localhost:5000/grid/" 
        width="100%" 
        height="800px"
        frameborder="0"
        style="border: 1px solid #ddd; border-radius: 8px;">
      </iframe>
    </div>
  `,
  styles: [`
    .container { padding: 20px; }
  `]
})
export class AppComponent {}

Comparison with ag-grid

Feature PyDataGrid ag-grid
Syntax <pydatagrid [gridOptions]="options"> <ag-grid-angular [gridOptions]="options">
Column Defs columnDefs: ColumnDef[] columnDefs: ColDef[]
Pagination pagination: true pagination: true
Sorting sortable: true sortable: true
Filtering filter: true filter: true
Data Binding [rowData]="data" or API [rowData]="data"
Backend Python Flask Any backend
License MIT (Free) Free/Commercial
Setup Time 5 minutes 10-15 minutes

Key Features

ag-grid Style API - Familiar property-based configuration
Column Definitions - Define columns with headerName, width, sortable, filter
Pagination - Built-in pagination with page size control
Sorting - Click column headers to sort
Filtering - Per-column filter inputs
Row Selection - Individual and bulk selection
Export - Export selected or all data to CSV
REST API - Python backend handles all data processing
TypeScript - Full TypeScript support with interfaces


Complete Working Example

See examples/angular-integration/ for a complete working Angular app with PyDataGrid integrated in ag-grid style.

# Run the example
cd examples/angular-integration
npm install
npm start

API Reference

DataGrid Class

Constructor

DataGrid(data, columns=None, column_config=None)

Methods

  • setVisibleColumns(columns): Set which columns to display
  • setPageSize(size): Set number of rows per page
  • setPage(page): Navigate to specific page
  • setSorting(column, direction): Set sorting configuration
  • setFilter(column, value): Apply filter to a column
  • clearFilters(): Remove all filters
  • getProcessedData(): Get current page data
  • getFilteredData(): Get all filtered data (no pagination)
  • getTotalRows(): Get total number of rows
  • getTotalPages(): Get total number of pages
  • toJson(): Export current page to JSON
  • toJsonAll(): Export all data to JSON
  • toCsv(selected_rows): Export data to CSV

Flask Integration

create_grid_blueprint()

create_grid_blueprint(data_source, name='datagrid', url_prefix='/grid')

Creates a Flask Blueprint with the following routes:

  • /: Main grid interface
  • /api/data: Get paginated data
  • /api/export: Export data as CSV

create_grid_app()

create_grid_app(data, host='127.0.0.1', port=5000, debug=True)

Creates a complete Flask application with the grid.

Configuration

Naming Conventions

The library follows these naming conventions:

  • PascalCase: Component names, interfaces, and type aliases
  • camelCase: Variables, functions, and methods
  • _underscore prefix: Private class members
  • ALL_CAPS: Constants

Performance Optimization

  • Default page size: 100 rows (configurable)
  • Column width adjusts to screen size
  • Text truncation with ellipsis for long content
  • Tooltip shows up to 200 characters on hover
  • Efficient filtering and sorting algorithms

Browser Support

  • Chrome (latest)
  • Firefox (latest)
  • Safari (latest)
  • Edge (latest)

Requirements

  • Python >= 3.7
  • Flask >= 2.0.0
  • flask-cors >= 3.0.0

License

MIT License - see LICENSE file for details

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

Support

For issues and questions, please open an issue on GitHub.

Changelog

Version 1.0.0

  • Initial release
  • Core grid functionality
  • Sorting and filtering
  • Pagination
  • Column visibility controls
  • Export to CSV
  • Flask integration
  • Responsive design

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pydatagrid-1.1.0.tar.gz (32.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pydatagrid-1.1.0-py3-none-any.whl (27.7 kB view details)

Uploaded Python 3

File details

Details for the file pydatagrid-1.1.0.tar.gz.

File metadata

  • Download URL: pydatagrid-1.1.0.tar.gz
  • Upload date:
  • Size: 32.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.8.9

File hashes

Hashes for pydatagrid-1.1.0.tar.gz
Algorithm Hash digest
SHA256 4468abcb738c38285c4393222e841d66c79e195c217aef1f30aad12105ea62c8
MD5 fd8aebccb77a85718a36334d1f6ada7d
BLAKE2b-256 3c8f496ce320ae6024d732b185bdb603fdb93b66ac0c48a9492e270fb73a1f22

See more details on using hashes here.

File details

Details for the file pydatagrid-1.1.0-py3-none-any.whl.

File metadata

  • Download URL: pydatagrid-1.1.0-py3-none-any.whl
  • Upload date:
  • Size: 27.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.8.9

File hashes

Hashes for pydatagrid-1.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 fb42d63635b8edb4eb364154f60c1192bd333edff2b27279cc2249608ecd50e8
MD5 8effa37e662424ada7e3cd3f7871ea20
BLAKE2b-256 40fd8d04feab39234da4017fdf16480f47dcf8daf3baa7f7c63e30c34e532d20

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page