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 seamless integration for Angular applications. You can use it as a component with property bindings or consume the REST API directly.

Method 1: Component Integration

This approach uses property bindings and event handlers for a familiar Angular development experience.

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 the Component

// 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</h1>
      
      <!-- Use PyDataGrid component -->
      <pydatagrid 
        [gridOptions]="gridOptions"
        style="height: 600px; width: 100%;">
      </pydatagrid>
    </div>
  `
})
export class AppComponent {
  // Define grid options
  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 {}

Key Features

Property-Based Configuration - Familiar Angular component API
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 integration.

# 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.1.tar.gz (32.0 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.1-py3-none-any.whl (27.4 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: pydatagrid-1.1.1.tar.gz
  • Upload date:
  • Size: 32.0 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.1.tar.gz
Algorithm Hash digest
SHA256 de32ec4d4a752cf5af22ca5f8b0305743f603a1b4471939e012c2c6e7aef9436
MD5 caeaf165bd683579f74d1e37b3693d69
BLAKE2b-256 31a377561230f6aef3464336544f9e5cb09fdf38e36027c8bd4a07f81d64c03c

See more details on using hashes here.

File details

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

File metadata

  • Download URL: pydatagrid-1.1.1-py3-none-any.whl
  • Upload date:
  • Size: 27.4 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.1-py3-none-any.whl
Algorithm Hash digest
SHA256 6d0c675346542776d89084b86d1db59c3087c0f40a6e4e4417a48d2ec60e46a1
MD5 24c1fe59ce58e507218a3d7c462efd52
BLAKE2b-256 113c2ff14403e5c7aec42b8bb985bb6c673e5593c25f2692c620f0326430781c

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