Skip to main content

A powerful data grid library with sorting, filtering, pagination, 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 can be easily integrated with Angular applications by consuming its REST API endpoints.

Step 1: Start the PyDataGrid Server

# server.py
from pydatagrid import create_grid_app

data = [
    # your data here
]

app = create_grid_app(data, host='127.0.0.1', port=5000)
app.run()

Step 2: Create Angular Service

// data-grid.service.ts
import { Injectable } from '@angular/core';
import { HttpClient, HttpParams } from '@angular/common/http';
import { Observable } from 'rxjs';

export interface GridConfig {
  columns: string[];
  visibleColumns: string[];
  currentPage: number;
  pageSize: number;
  totalPages: number;
  totalRows: number;
  sortColumn: string | null;
  sortDirection: string;
  filters: { [key: string]: string };
  data: any[];
}

@Injectable({
  providedIn: 'root'
})
export class DataGridService {
  private apiUrl = 'http://127.0.0.1:5000/grid/api';

  constructor(private http: HttpClient) {}

  getData(
    page: number = 1,
    pageSize: number = 100,
    sortColumn: string = '',
    sortDirection: string = 'asc',
    filters: { [key: string]: string } = {},
    visibleColumns: string[] = []
  ): Observable<GridConfig> {
    let params = new HttpParams()
      .set('page', page.toString())
      .set('page_size', pageSize.toString())
      .set('sort_column', sortColumn)
      .set('sort_direction', sortDirection);

    if (visibleColumns.length > 0) {
      params = params.set('visible_columns', JSON.stringify(visibleColumns));
    }

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

    return this.http.get<GridConfig>(`${this.apiUrl}/data`, { params });
  }

  exportData(
    exportType: 'all' | 'selected',
    sortColumn: string = '',
    sortDirection: string = 'asc',
    filters: { [key: string]: string } = {},
    selectedRows: number[] = []
  ): string {
    let params = new HttpParams()
      .set('export_type', exportType)
      .set('sort_column', sortColumn)
      .set('sort_direction', sortDirection);

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

    if (selectedRows.length > 0) {
      params = params.set('selected_rows', JSON.stringify(selectedRows));
    }

    return `${this.apiUrl}/export?${params.toString()}`;
  }
}

Step 3: Create Angular Component

// data-grid.component.ts
import { Component, OnInit } from '@angular/core';
import { DataGridService, GridConfig } from './data-grid.service';

@Component({
  selector: 'app-data-grid',
  templateUrl: './data-grid.component.html',
  styleUrls: ['./data-grid.component.css']
})
export class DataGridComponent implements OnInit {
  gridConfig: GridConfig | null = null;
  selectedRows: Set<number> = new Set();
  loading: boolean = false;

  constructor(private dataGridService: DataGridService) {}

  ngOnInit(): void {
    this.loadData();
  }

  loadData(): void {
    this.loading = true;
    const page = this.gridConfig?.currentPage || 1;
    const sortColumn = this.gridConfig?.sortColumn || '';
    const sortDirection = this.gridConfig?.sortDirection || 'asc';
    const filters = this.gridConfig?.filters || {};
    const visibleColumns = this.gridConfig?.visibleColumns || [];

    this.dataGridService.getData(
      page,
      100,
      sortColumn,
      sortDirection,
      filters,
      visibleColumns
    ).subscribe({
      next: (config) => {
        this.gridConfig = config;
        this.loading = false;
      },
      error: (error) => {
        console.error('Error loading data:', error);
        this.loading = false;
      }
    });
  }

  onSort(column: string): void {
    if (!this.gridConfig) return;

    if (this.gridConfig.sortColumn === column) {
      this.gridConfig.sortDirection = 
        this.gridConfig.sortDirection === 'asc' ? 'desc' : 'asc';
    } else {
      this.gridConfig.sortColumn = column;
      this.gridConfig.sortDirection = 'asc';
    }
    
    this.loadData();
  }

  onFilter(column: string, value: string): void {
    if (!this.gridConfig) return;

    if (value) {
      this.gridConfig.filters[column] = value;
    } else {
      delete this.gridConfig.filters[column];
    }
    
    this.gridConfig.currentPage = 1;
    this.loadData();
  }

  onPageChange(page: number): void {
    if (!this.gridConfig) return;
    this.gridConfig.currentPage = page;
    this.selectedRows.clear();
    this.loadData();
  }

  toggleRowSelection(index: number): void {
    if (this.selectedRows.has(index)) {
      this.selectedRows.delete(index);
    } else {
      this.selectedRows.add(index);
    }
  }

  selectAll(checked: boolean): void {
    if (checked) {
      this.gridConfig?.data.forEach((_, index) => 
        this.selectedRows.add(index)
      );
    } else {
      this.selectedRows.clear();
    }
  }

  exportSelected(): void {
    if (!this.gridConfig || this.selectedRows.size === 0) return;

    const url = this.dataGridService.exportData(
      'selected',
      this.gridConfig.sortColumn || '',
      this.gridConfig.sortDirection,
      this.gridConfig.filters,
      Array.from(this.selectedRows)
    );

    window.open(url, '_blank');
  }

  exportAll(): void {
    if (!this.gridConfig) return;

    const url = this.dataGridService.exportData(
      'all',
      this.gridConfig.sortColumn || '',
      this.gridConfig.sortDirection,
      this.gridConfig.filters
    );

    window.open(url, '_blank');
  }
}

Step 4: Angular Template

<!-- data-grid.component.html -->
<div class="grid-container" *ngIf="gridConfig">
  <div class="grid-header">
    <button (click)="exportSelected()" 
            [disabled]="selectedRows.size === 0">
      Export Selected
    </button>
    <button (click)="exportAll()">
      Export All
    </button>
  </div>

  <table class="data-table">
    <thead>
      <tr>
        <th>
          <input type="checkbox" 
                 [checked]="selectedRows.size === gridConfig.data.length"
                 (change)="selectAll($event.target.checked)">
        </th>
        <th *ngFor="let column of gridConfig.visibleColumns"
            (click)="onSort(column)"
            class="sortable">
          {{ column }}
          <span *ngIf="gridConfig.sortColumn === column">
            {{ gridConfig.sortDirection === 'asc' ? '▲' : '▼' }}
          </span>
        </th>
      </tr>
    </thead>
    <tbody>
      <tr *ngFor="let row of gridConfig.data; let i = index"
          [class.selected]="selectedRows.has(i)">
        <td>
          <input type="checkbox"
                 [checked]="selectedRows.has(i)"
                 (change)="toggleRowSelection(i)">
        </td>
        <td *ngFor="let column of gridConfig.visibleColumns">
          {{ row[column] }}
        </td>
      </tr>
    </tbody>
  </table>

  <div class="pagination">
    <button (click)="onPageChange(1)" 
            [disabled]="gridConfig.currentPage === 1">
      First
    </button>
    <button (click)="onPageChange(gridConfig.currentPage - 1)"
            [disabled]="gridConfig.currentPage === 1">
      Previous
    </button>
    <span>Page {{ gridConfig.currentPage }} of {{ gridConfig.totalPages }}</span>
    <button (click)="onPageChange(gridConfig.currentPage + 1)"
            [disabled]="gridConfig.currentPage === gridConfig.totalPages">
      Next
    </button>
    <button (click)="onPageChange(gridConfig.totalPages)"
            [disabled]="gridConfig.currentPage === gridConfig.totalPages">
      Last
    </button>
  </div>
</div>

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.0.0.tar.gz (24.8 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.0.0-py3-none-any.whl (22.0 kB view details)

Uploaded Python 3

File details

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

File metadata

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

File hashes

Hashes for pydatagrid-1.0.0.tar.gz
Algorithm Hash digest
SHA256 9ec9ae003f691117e162661d886ad47f5e0a8a9574170de5c026d7bb2b7d0300
MD5 7e82985c167955b490731ccea71511f4
BLAKE2b-256 e3a02757c8e12718ed34b0ca89b50423ea4ec4b3b80ad583b633c40c5b682a23

See more details on using hashes here.

File details

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

File metadata

  • Download URL: pydatagrid-1.0.0-py3-none-any.whl
  • Upload date:
  • Size: 22.0 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.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 4c0365e2827de6a70f6438478ebb55b05b1bbf2b6e1c97a555b78ad012d11088
MD5 fadddbe53048186b4f48c44885248eec
BLAKE2b-256 1750a369f7e1bb650005d06d7aa2942cf8abd85138d34476782d4be70f3d04c5

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