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
- Quick Start Guide - Get started in 5 minutes
- Deployment Guide - Docker, PyPI, Angular & Python integration
- Examples - Code examples and use cases
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 displaysetPageSize(size): Set number of rows per pagesetPage(page): Navigate to specific pagesetSorting(column, direction): Set sorting configurationsetFilter(column, value): Apply filter to a columnclearFilters(): Remove all filtersgetProcessedData(): Get current page datagetFilteredData(): Get all filtered data (no pagination)getTotalRows(): Get total number of rowsgetTotalPages(): Get total number of pagestoJson(): Export current page to JSONtoJsonAll(): Export all data to JSONtoCsv(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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4468abcb738c38285c4393222e841d66c79e195c217aef1f30aad12105ea62c8
|
|
| MD5 |
fd8aebccb77a85718a36334d1f6ada7d
|
|
| BLAKE2b-256 |
3c8f496ce320ae6024d732b185bdb603fdb93b66ac0c48a9492e270fb73a1f22
|
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fb42d63635b8edb4eb364154f60c1192bd333edff2b27279cc2249608ecd50e8
|
|
| MD5 |
8effa37e662424ada7e3cd3f7871ea20
|
|
| BLAKE2b-256 |
40fd8d04feab39234da4017fdf16480f47dcf8daf3baa7f7c63e30c34e532d20
|