Skip to content

Commit 096f1b2

Browse files
jonpspriclaude
andauthored
feat: implement singleton pattern for SecureExpressionEvaluator (#105)
* feat: implement singleton pattern for SecureExpressionEvaluator Added performance optimization through singleton pattern implementation: • **Singleton Pattern**: Replaced per-call SecureExpressionEvaluator() instantiation with get_secure_expression_evaluator() singleton access for improved performance • **Performance Benefits**: Eliminates expensive object initialization overhead on every expression evaluation while maintaining thread safety • **API Compatibility**: Updated evaluate_expression_safely() and validate_expression_safety() to use singleton pattern while preserving existing function interfaces • **Testing Support**: Added reset_secure_expression_evaluator() function for test isolation and create_secure_expression_evaluator() factory function • **Memory Efficiency**: Single evaluator instance reused across all expression operations instead of creating new instances repeatedly • **Quality Assurance**: All 997 tests passing with ruff and mypy compliance maintained This follows the same singleton pattern established for session management, providing consistent architecture patterns and improved resource utilization for expression-heavy operations throughout DataBeak. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * Interim commit for session dependency injection cleanup * feat: implement comprehensive Pandera validation integration Major enhancements to validation system: - Added pandera dependency for industry-standard validation framework - Replaced ColumnValidationRules with comprehensive Pandera Field/Check capabilities - Implemented Pandera validation logic replacing manual validation - Enhanced documentation with official Pandera API references - Removed duplicate functionality (unused pandera_schemas module, 179 tests) - Streamlined ValidationError model (removed 9 legacy fields) - Updated all validation tests for new Pandera field names Quality verification: - MyPy: Perfect compliance (validation components) - Tests: All 41 validation tests passing - Functionality: Robust validation using industry-standard framework This establishes DataBeak validation on professional-grade Pandera framework with comprehensive rule coverage and clean architecture. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: add pandera to mypy pre-commit dependencies Resolves mypy hook failure by adding pandera>=0.22.0 to the mypy additional_dependencies list in pre-commit configuration. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: improve YAML formatting in pre-commit config Breaks long description lines to comply with yamllint line length rules using proper YAML multi-line format. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: improve pre-commit configuration and markdown formatting - Remove docformatter from pre-commit hooks to eliminate ruff conflicts - Add py.typed marker file for proper MyPy module resolution - Configure MyPy pre-commit hook to check source only, add local package dependency - Add mdformat-ruff integration for consistent Python code block formatting - Apply comprehensive mdformat fixes across all project documentation - Update quality-gate-runner documentation to remove docformatter references 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * refactor: implement singleton patterns for session and settings management - Centralize SessionManager creation with thread-safe singleton - Implement singleton pattern for DataBeakSettings initialization - Standardize get_session_manager() calls across all servers - Remove redundant SessionManager instantiations - Improve dependency injection patterns throughout codebase - Reduce code duplication and ensure consistent state management 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * Final bug fixes and tweaks * Reset is also thread-safe --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 712b7fc commit 096f1b2

77 files changed

Lines changed: 1199 additions & 1662 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.claude/agents/mcp-tool-generator.md

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -64,22 +64,25 @@ logger = logging.getLogger(__name__)
6464
# PYDANTIC MODELS (Domain-specific, self-contained)
6565
# ============================================================================
6666

67+
6768
class DomainResult(BaseModel):
6869
"""Response model for domain operations."""
6970

70-
model_config = ConfigDict(extra='forbid')
71+
model_config = ConfigDict(extra="forbid")
7172

7273
session_id: str
7374
success: bool = True
7475
data: dict[str, Any] = Field(default_factory=dict)
7576

77+
7678
class DomainRule(BaseModel):
7779
"""Base class for domain rules."""
7880

79-
model_config = ConfigDict(extra='forbid')
81+
model_config = ConfigDict(extra="forbid")
8082

8183
type: str
8284

85+
8386
class SpecificRule(DomainRule):
8487
"""Specific rule implementation."""
8588

@@ -94,16 +97,18 @@ class SpecificRule(DomainRule):
9497
raise ValueError("Threshold must be between 0.0 and 1.0")
9598
return v
9699

100+
97101
# Discriminated union for automatic type conversion
98102
DomainRuleType = Annotated[
99103
SpecificRule, # Add more rule types here
100-
Field(discriminator="type")
104+
Field(discriminator="type"),
101105
]
102106

103107
# ============================================================================
104108
# DOMAIN LOGIC (Synchronous for computational operations)
105109
# ============================================================================
106110

111+
107112
def process_domain_operation(
108113
session_id: str,
109114
rules: list[DomainRuleType] | None = None,
@@ -147,18 +152,16 @@ def process_domain_operation(
147152
"operation": "domain_operation",
148153
"rules_count": len(rules),
149154
"results_count": len(results),
150-
}
155+
},
151156
)
152157

153-
return DomainResult(
154-
session_id=session_id,
155-
data={"results": results, "total": len(results)}
156-
)
158+
return DomainResult(session_id=session_id, data={"results": results, "total": len(results)})
157159

158160
except Exception as e:
159161
logger.error(f"Error in domain operation: {e!s}")
160162
raise ToolError(f"Error processing domain operation: {e!s}") from e
161163

164+
162165
# ============================================================================
163166
# FASTMCP SERVER SETUP
164167
# ============================================================================
@@ -193,23 +196,24 @@ mcp.mount(domain_server)
193196
```python
194197
# Base class with discriminator
195198
class BaseRule(BaseModel):
196-
model_config = ConfigDict(extra='forbid')
199+
model_config = ConfigDict(extra="forbid")
197200
type: str
198201

202+
199203
# Specific implementations
200204
class TypeARule(BaseRule):
201205
type: Literal["type_a"] = "type_a"
202206
param1: str
203207

208+
204209
class TypeBRule(BaseRule):
205210
type: Literal["type_b"] = "type_b"
206211
param2: int = Field(ge=0)
207212

213+
208214
# Discriminated union for automatic conversion
209-
RuleType = Annotated[
210-
TypeARule | TypeBRule,
211-
Field(discriminator="type")
212-
]
215+
RuleType = Annotated[TypeARule | TypeBRule, Field(discriminator="type")]
216+
213217

214218
# Usage in function
215219
def process_rules(rules: list[RuleType]) -> Results:
@@ -224,12 +228,10 @@ def process_rules(rules: list[RuleType]) -> Results:
224228
class DomainModel(BaseModel):
225229
"""Model with comprehensive validation."""
226230

227-
model_config = ConfigDict(extra='forbid')
231+
model_config = ConfigDict(extra="forbid")
228232

229233
# Use Literal for known values (compile-time safety)
230-
status: Literal["active", "inactive", "pending"] = Field(
231-
description="Processing status"
232-
)
234+
status: Literal["active", "inactive", "pending"] = Field(description="Processing status")
233235

234236
# Use field validation for complex rules
235237
pattern: str | None = Field(None, description="Regex pattern")
@@ -242,6 +244,7 @@ class DomainModel(BaseModel):
242244
return v
243245

244246
import re
247+
245248
try:
246249
re.compile(v)
247250
return v
@@ -343,6 +346,7 @@ class IntegrationTestCase(unittest.IsolatedAsyncioTestCase):
343346
except Exception:
344347
pass
345348

349+
346350
class TestDomainIntegration(IntegrationTestCase):
347351
"""Integration tests for domain operations."""
348352

.claude/agents/python-type-optimizer.md

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,18 +53,21 @@ return {"success": False, "error": str(e)}
5353
```python
5454
from typing import TypedDict, Literal
5555

56+
5657
class OperationSuccess(TypedDict):
5758
success: Literal[True]
5859
data: dict[str, CellValue] | list[dict[str, CellValue]]
5960
session_id: str
6061
rows_affected: int | None
6162
message: str
6263

64+
6365
class OperationError(TypedDict):
6466
success: Literal[False]
6567
error: str | dict[str, str]
6668
session_id: str | None
6769

70+
6871
OperationResult = OperationSuccess | OperationError
6972
```
7073

@@ -98,6 +101,7 @@ class NumericColumnStats(TypedDict):
98101
skewness: float
99102
kurtosis: float
100103

104+
101105
class StatisticsResult(TypedDict):
102106
success: Literal[True]
103107
statistics: dict[str, NumericColumnStats]
@@ -114,11 +118,10 @@ class AutoSaveConfigDict(TypedDict):
114118
backup_directory: str
115119
format: Literal["csv", "tsv", "json", "excel", "parquet"]
116120

121+
117122
class FilterCondition(TypedDict):
118123
column: str
119-
operator: Literal[
120-
"==", "!=", ">", "<", ">=", "<=", "contains", "startswith", "endswith"
121-
]
124+
operator: Literal["==", "!=", ">", "<", ">=", "<=", "contains", "startswith", "endswith"]
122125
value: CellValue
123126
```
124127

@@ -187,23 +190,28 @@ CellValue = str | int | float | bool | None
187190
RowData = dict[str, CellValue]
188191
DataFrame = pd.DataFrame
189192

193+
190194
# Base operation result types
191195
class BaseOperationResult(TypedDict):
192196
success: bool
193197
session_id: str | None
194198

199+
195200
class SuccessfulOperationResult(BaseOperationResult):
196201
success: Literal[True]
197202
message: str
198203
rows_affected: int | None
199204
columns_affected: list[str] | None
200205

206+
201207
class FailedOperationResult(BaseOperationResult):
202208
success: Literal[False]
203209
error: str | dict[str, str]
204210

211+
205212
OperationResult = SuccessfulOperationResult | FailedOperationResult
206213

214+
207215
# Session types
208216
class SessionMetadata(TypedDict):
209217
created_at: str
@@ -212,6 +220,7 @@ class SessionMetadata(TypedDict):
212220
auto_save_enabled: bool
213221
file_path: str | None
214222

223+
215224
# Configuration types
216225
class DataBeakSettings(TypedDict):
217226
session_timeout_minutes: int
@@ -303,11 +312,13 @@ class ToolSuccessResponse(TypedDict):
303312
metadata: dict[str, int | str] # Not Any
304313
session_id: str
305314

315+
306316
class ToolErrorResponse(TypedDict):
307317
success: Literal[False]
308318
error: dict[str, str] # Structured error info
309319
session_id: str | None
310320

321+
311322
ToolResponse = ToolSuccessResponse | ToolErrorResponse
312323
```
313324

@@ -322,7 +333,7 @@ class DataBeakErrorInfo(TypedDict):
322333
"NoDataLoadedError",
323334
"ColumnNotFoundError",
324335
"InvalidParameterError",
325-
"DataProcessingError"
336+
"DataProcessingError",
326337
]
327338
message: str
328339
details: dict[str, str] | None

.claude/agents/quality-gate-runner.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,6 @@ uv run pre-commit run --all-files
8989
# Fix specific hook types
9090
uv run pre-commit run ruff --all-files
9191
uv run pre-commit run ruff-format --all-files
92-
uv run pre-commit run docformatter --all-files
9392
```
9493

9594
#### Individual Tool Auto-fixes
@@ -116,7 +115,6 @@ The project uses a comprehensive pre-commit setup with these tools:
116115
- **Ruff**: Linting and formatting (replaces Black, isort, autoflake, pyupgrade)
117116
- **MyPy**: Type checking with pandas-stubs
118117
- **Bandit**: Security scanning
119-
- **docformatter**: Docstring formatting
120118
- **markdownlint**: Documentation quality
121119
- **License insertion**: Automatic Apache license headers
122120

@@ -184,6 +182,7 @@ from fastmcp import Context # TC003 error
184182

185183
# Fix: Move to TYPE_CHECKING block
186184
from typing import TYPE_CHECKING
185+
187186
if TYPE_CHECKING:
188187
from fastmcp import Context
189188
```
@@ -213,8 +212,9 @@ assert "DataBeak" in result["message"]
213212

214213
```python
215214
# Add coverage-focused test files
216-
tests/test_analytics_coverage.py
217-
tests/test_validation_coverage.py
215+
tests / test_analytics_coverage.py
216+
tests / test_validation_coverage.py
217+
218218

219219
# Test error handling paths
220220
async def test_handles_empty_dataframe(self, empty_session):
@@ -234,6 +234,7 @@ async def test_session_with_data():
234234
result = await load_csv_from_content(csv_content)
235235
return result["session_id"]
236236

237+
237238
# Error handling test pattern
238239
async def test_invalid_session_handling(self):
239240
result = await tool_function("invalid_session", "param")

.claude/agents/test-coverage-analyzer.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ class TestStatisticsServer:
109109
@pytest.fixture
110110
def mock_session(self):
111111
"""Create mock session."""
112-
with patch('get_session_manager') as mock:
112+
with patch("get_session_manager") as mock:
113113
session = MagicMock()
114114
session.data_session.df = pd.DataFrame(test_data)
115115
mock.return_value.get_session.return_value = session
@@ -175,14 +175,15 @@ Create tests in the appropriate tier:
175175
# tests/unit/servers/test_statistics_server.py
176176
async def test_handles_empty_dataframe(self):
177177
"""Test statistics with empty DataFrame."""
178-
with patch('get_session_manager') as mock:
178+
with patch("get_session_manager") as mock:
179179
session = MagicMock()
180180
session.data_session.df = pd.DataFrame()
181181
mock.return_value.get_session.return_value = session
182182

183183
with pytest.raises(ToolError, match="No data"):
184184
await get_statistics("session-id")
185185

186+
186187
# Integration test for workflow
187188
# tests/integration/test_analytics_workflow.py
188189
async def test_statistics_to_export_workflow(self):
@@ -236,6 +237,7 @@ async def test_statistics_to_export_workflow(self):
236237
# Wait for timeout
237238
# Verify cleanup
238239

240+
239241
async def test_concurrent_sessions(self):
240242
"""Test concurrent session operations."""
241243
# Create multiple sessions
@@ -251,6 +253,7 @@ async def test_statistics_to_export_workflow(self):
251253
# Test type conversion failures
252254
# Test validation error messages
253255

256+
254257
async def test_malformed_input(self):
255258
"""Test malformed input handling."""
256259
# Test parsing errors

.claude/planning/REFACTORING_CHECKLIST.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ async def filter_rows(
2727
# Implementation here
2828
pass
2929

30+
3031
# Register at the end of file
3132
server = FastMCP("Server Name")
3233
server.tool(name="filter_rows")(filter_rows)
@@ -148,6 +149,7 @@ Is the model used by multiple servers?
148149
# In server file
149150
class ColumnOperationResult(BaseModel):
150151
"""Result of a column operation (local to this server)."""
152+
151153
session_id: str
152154
operation: str
153155
rows_affected: int
@@ -160,6 +162,7 @@ class ColumnOperationResult(BaseModel):
160162
# In models/tool_responses.py (shared across servers)
161163
class LoadResult(BaseModel):
162164
"""Result of loading data (used by multiple servers)."""
165+
163166
session_id: str
164167
success: bool
165168
rows_affected: int
@@ -188,6 +191,7 @@ Is the exception used by multiple servers?
188191
# In server file
189192
class FilterExpressionError(Exception):
190193
"""Error in filter expression syntax (specific to this server)."""
194+
191195
pass
192196
```
193197

@@ -248,13 +252,11 @@ async def test_session() -> str:
248252
result = await load_csv_from_content(csv_content)
249253
return result.session_id
250254

255+
251256
@pytest.mark.asyncio
252257
async def test_filter_rows_basic(test_session):
253258
"""Test basic filtering functionality."""
254-
result = await filter_rows(
255-
test_session,
256-
[{"column": "age", "operator": ">", "value": 25}]
257-
)
259+
result = await filter_rows(test_session, [{"column": "age", "operator": ">", "value": 25}])
258260
assert result.rows_after == 1
259261
assert result.rows_filtered == 1
260262
```

.github/ISSUE_TEMPLATE/feature_request.md

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,7 @@ any alternative solutions or features you've considered.
2626
```python
2727
# Example usage
2828
databeak.new_feature(
29-
input_file="data.csv",
30-
operation="your_feature",
31-
parameters={"param1": "value1"}
29+
input_file="data.csv", operation="your_feature", parameters={"param1": "value1"}
3230
)
3331
```
3432

0 commit comments

Comments
 (0)