diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..ab9349f --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,189 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +A Python package for parsing boolean conditional expressions from strings using pyparsing. Converts string expressions like "x > 1 and y < 2" into object representations and SQLAlchemy filter conditions. + +**Core Components:** +- **Parser**: Base parser for boolean conditional expressions +- **SQLAParser**: Specialized parser that converts strings to SQLAlchemy filter clauses +- **Clauses**: pyparsing grammar definitions for conditionals, operators, and boolean logic +- **Actions**: Classes that control parsing behavior for each clause type +- **Mixins**: SQLAMixin provides SQLAlchemy filter conversion capabilities + +## Development Commands + +### Testing +```bash +# Run all tests with coverage +pytest + +# Run tests with coverage report +pytest --cov boolean_parser --cov-report html + +# Run specific test file +pytest tests/parsers/test_parser.py + +# Run specific test +pytest tests/parsers/test_parser.py::test_name -v +``` + +### Code Quality +```bash +# Lint with flake8 +flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics +flake8 . --count --exit-zero --max-complexity=10 --max-line-length=99 --statistics + +# Sort imports (isort config in setup.cfg) +isort boolean_parser/ +``` + +### Documentation +```bash +# Build Sphinx docs +invoke docs.build + +# Build docs with clean +invoke docs.build --clean + +# Show built docs in browser +invoke docs.show + +# Clean docs build directory +invoke docs.clean +``` + +### Package Management +```bash +# Install in development mode +pip install -e .[dev] + +# Install with docs extras +pip install -e .[docs] + +# Clean build artifacts +invoke clean + +# Deploy to PyPI (after invoke clean) +invoke deploy +``` + +## Architecture + +### Parsing Flow +1. **Input String** → `Parser.parse()` or `SQLAParser.parse()` +2. **pyparsing Grammar** → Clauses define pattern matching (condition, between_cond, words) +3. **Parse Actions** → Actions convert parsed tokens to objects (Condition, Word) +4. **Boolean Logic** → infixNotation handles NOT→AND→OR precedence using BoolNot, BoolAnd, BoolOr +5. **Output Object** → Returns structured representation with `.params` and `.conditions` attributes + +### Parser Class Hierarchy +``` +Parser (base class - boolean_parser/parsers/base.py) + ├─ Clauses: condition, between_cond, words + ├─ Actions: Condition, Word + └─ Booleans: BoolNot, BoolAnd, BoolOr + +SQLAParser (extends Parser - boolean_parser/parsers/sqla.py) + ├─ Clauses: condition, between_cond (no words clause) + ├─ Actions: SQLACondition (uses SQLAMixin) + └─ Booleans: SQLANot, SQLAAnd, SQLAOr (with .filter() method) +``` + +### SQLAlchemy Integration Pattern +The SQLAParser uses a mixin pattern to add SQLAlchemy capabilities: +- **SQLAMixin** (boolean_parser/mixins/sqla.py): Provides `filter()` method for converting parsed conditions to SQLAlchemy filter expressions +- **SQLACondition** (inherits from both Condition and SQLAMixin): Parses "table.column operator value" syntax +- **SQLBoolBase**: Boolean classes override to call `filter()` on nested conditions with sqlalchemy.sql operators (and_, or_, not_) + +### Clause Definitions (boolean_parser/clauses.py) +- **condition**: `name operator value` (e.g., "x > 5", "table.column = 'test'") +- **between_cond**: `name between value1 and value2` (e.g., "age between 18 and 65") +- **words**: Simple word tokens (used by base Parser only) +- **Operators**: ==, <=, <, >, >=, =, !=, &, | plus special handling for wildcards (*) and NULL + +### Parser Customization +Use `Parser.build_parser(clauses, actions, bools)` to create custom parsers with: +- Custom clause definitions (pyparsing elements) +- Custom actions (classes/functions for parse behavior) +- Custom boolean logic handlers (must be [NotClass, AndClass, OrClass]) + +## Key Implementation Details + +### String Condition Syntax +- **Base**: `parameter operator value` → "x > 1", "name = 'test*'" (wildcards supported) +- **SQLAlchemy**: `table.parameter operator value` → "users.age > 18", "users.name = '*smith'" +- **Between**: `parameter between value1 and value2` → "age between 18 and 65" +- **Boolean Logic**: Join with "and", "or", "not" → "x > 1 and y < 2 or not z = 3" + +### SQLAlchemy Filter Conversion +When calling `result.filter(ModelClass)`: +1. Matches parsed parameter names to ModelClass attributes via tablename +2. Handles type casting (int, float, bool, date, datetime) based on column type +3. Converts operators: `=` becomes LIKE with wildcards, `==` is exact equality +4. Special handling: NULL values, PostgreSQL arrays, bitwise operations +5. Uses bindparam for safe parameter binding + +## Testing Strategy + +Tests use pytest with fixtures in tests/conftest.py: +- **database.py**: In-memory SQLite database setup +- **models.py**: SQLAlchemy test models (Table, Thing, etc.) +- **factory_boy**: Factories for test data generation via pytest-factoryboy +- **Test structure**: tests/parsers/ for parser tests, tests/test_*.py for unit tests + +## Configuration Files + +- **setup.cfg**: Package metadata, dependencies, tool configs (isort, flake8, pytest, coverage) +- **tasks.py**: Invoke tasks for docs and deployment +- **.github/workflows/**: CI runs tests, linting, and Sphinx doc builds on push/PR + +## Version & Dependencies + +- **Python**: >= 3.6 +- **Required**: six >= 1.16.0, pyparsing >= 2.4, sqlalchemy >= 1.4.0 +- **Dev**: pytest, pytest-cov, factory_boy, pytest-factoryboy, ipython +- **Docs**: Sphinx, sphinx_bootstrap_theme, sphinx-issues + +## Common Patterns + +### Creating a Custom Parser +```python +from boolean_parser.parsers import Parser +from boolean_parser.clauses import condition, words +from boolean_parser.actions.clause import Condition, Word + +clauses = [condition, words] +actions = [Condition, Word] +Parser.build_parser(clauses=clauses, actions=actions) +``` + +### Using the Convenience Function +```python +from boolean_parser import parse + +# Use base parser +result = parse('x > 1 and y < 2', base='base') + +# Use SQLAlchemy parser (default) +result = parse('table.x > 5', base='sqla') +``` + +### SQLAlchemy Query Pattern +```python +from boolean_parser.parsers import SQLAParser +from models import TableModel +from database import session + +# Parse string condition +parser = SQLAParser('table.x > 5 and table.y < 2') +result = parser.parse() + +# Convert to SQLAlchemy filter +filter_condition = result.filter(TableModel) + +# Use in query +query = session.query(TableModel).filter(filter_condition).all() +``` diff --git a/README.md b/README.md index af82255..038ec0c 100644 --- a/README.md +++ b/README.md @@ -6,12 +6,260 @@ [![Coverage Status](https://coveralls.io/repos/github/havok2063/boolean_parser/badge.svg?branch=master)](https://coveralls.io/github/havok2063/boolean_parser?branch=master) [![codecov](https://codecov.io/gh/havok2063/boolean_parser/branch/master/graph/badge.svg)](https://codecov.io/gh/havok2063/boolean_parser) +A Python package for parsing string-based boolean conditional expressions and converting them to structured objects or SQLAlchemy filters. -Python package for parsing a string with conditional expressions joined with boolean logic. Uses the [pyparsing](https://github.com/pyparsing/pyparsing) package to construct grammatical clauses representing conditional expression, e.g. "x > 1 and y < 2". String conditional expressions can then be parsed into object representation to be handled downstream. Can -convert string boolean expressions into SQLAlchemy filter conditions. +## Overview -Documentation: https://boolean-parser.readthedocs.io/en/latest/ +`boolean_parser` uses [pyparsing](https://github.com/pyparsing/pyparsing) to parse conditional expressions like `"x > 1 and y < 2"` into structured Python objects. It's particularly useful for: -Parsers: - - Parser: core parser for handling parsing complex boolean conditional expressions - - SQLParser: parser that enables converting a string conditional into a SQLAlchemy filter clause +- **Dynamic query building**: Convert user-input strings to database queries +- **Configuration parsing**: Parse complex conditional logic from config files +- **Rule engines**: Implement flexible rule-based systems +- **SQLAlchemy integration**: Seamlessly convert strings to SQLAlchemy filter conditions + +## Features + +- ✅ Parse complex boolean expressions with `AND`, `OR`, and `NOT` operators +- ✅ Support for comparison operators: `>`, `<`, `>=`, `<=`, `=`, `==`, `!=` +- ✅ Special `BETWEEN` syntax for range conditions +- ✅ Direct conversion to SQLAlchemy filter clauses +- ✅ Wildcard support (`*`) for pattern matching +- ✅ NULL value handling +- ✅ Type casting for dates, datetimes, booleans, integers, and floats +- ✅ Customizable parsers with your own grammar and actions + +## Installation + +```bash +pip install boolean-parser +``` + +### Requirements + +- Python >= 3.6 +- pyparsing >= 2.4 +- sqlalchemy >= 1.4.0 +- six >= 1.16.0 + +## Quick Start + +### Basic Parsing + +```python +from boolean_parser import parse + +# Parse a simple condition +result = parse('x > 5', base='base') +print(result.params) # ['x'] +print(result.conditions) # x>5 + +# Parse complex boolean logic +result = parse('x > 1 and y < 2 or not z = 3', base='base') +print(result.params) # ['x', 'y', 'z'] +``` + +### SQLAlchemy Integration + +```python +from boolean_parser import SQLAParser +from sqlalchemy import create_engine, Column, Integer, String +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.orm import sessionmaker + +# Define your SQLAlchemy model +Base = declarative_base() + +class User(Base): + __tablename__ = 'users' + id = Column(Integer, primary_key=True) + age = Column(Integer) + name = Column(String) + +# Create a session +engine = create_engine('sqlite:///:memory:') +Base.metadata.create_all(engine) +Session = sessionmaker(bind=engine) +session = Session() + +# Parse and apply filter +parser = SQLAParser('users.age > 21 and users.name = "John*"') +result = parser.parse() +filter_condition = result.filter(User) + +# Use in query +adults_named_john = session.query(User).filter(filter_condition).all() +``` + +## Syntax Guide + +### Basic Conditions + +| Syntax | Description | Example | +|--------|-------------|---------| +| `parameter operator value` | Simple condition | `age > 18` | +| `table.parameter operator value` | SQLAlchemy condition | `users.age >= 21` | +| `parameter between value1 and value2` | Range condition | `age between 18 and 65` | + +### Operators + +| Operator | Description | +|----------|-------------| +| `>`, `<`, `>=`, `<=` | Comparison operators | +| `=` | Equality with wildcard support (LIKE in SQL) | +| `==` | Exact equality | +| `!=` | Not equal | +| `and`, `or`, `not` | Boolean logic operators | + +### Special Values + +- **NULL**: Use `NULL` or `null` for null values (e.g., `name = null`) +- **Wildcards**: Use `*` for pattern matching with `=` operator (e.g., `name = "John*"`) +- **Booleans**: `True`, `False`, `t`, `f`, `yes`, `no`, `1`, `0` +- **Dates**: ISO format `2020-01-01` +- **Datetimes**: ISO format `2020-01-01T12:00:00` + +### Complex Examples + +```python +# Multiple conditions with AND/OR +parse('age > 18 and (name = "John*" or name = "Jane*")') + +# BETWEEN syntax +parse('age between 21 and 65 and status = "active"') + +# NOT operator +parse('not (status = "deleted" or status = "suspended")') + +# NULL handling +parse('email != null and verified = True') +``` + +## Advanced Usage + +### Creating Custom Parsers + +```python +from boolean_parser.parsers import Parser +from boolean_parser.clauses import condition, words +from boolean_parser.actions.clause import Condition, Word + +# Define custom clauses and actions +clauses = [condition, words] +actions = [Condition, Word] + +# Build custom parser +Parser.build_parser(clauses=clauses, actions=actions) +``` + +### Using the Convenience Function + +```python +from boolean_parser import parse + +# Use base parser +result = parse('x > 1 and y < 2', base='base') + +# Use SQLAlchemy parser (default) +result = parse('table.x > 5', base='sqla') +``` + +### Working with Parsed Results + +```python +from boolean_parser import Parser + +parser = Parser('x > 5 and y < 2') +result = parser.parse() + +# Access parameters +print(result.params) # ['x', 'y'] + +# Access conditions +print(result.conditions) # [x>5, y<2] +``` + +## Documentation + +Full documentation is available at: https://boolean-parser.readthedocs.io/en/latest/ + +## Development + +### Setup + +```bash +# Clone the repository +git clone https://github.com/havok2063/boolean_parser.git +cd boolean_parser + +# Install in development mode +pip install -e .[dev] +``` + +### Running Tests + +```bash +# Run all tests with coverage +pytest + +# Run with coverage report +pytest --cov boolean_parser --cov-report html + +# Run specific test file +pytest tests/parsers/test_parser.py +``` + +### Code Quality + +```bash +# Lint with flake8 +flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics + +# Sort imports +isort boolean_parser/ +``` + +### Building Documentation + +```bash +# Install docs dependencies +pip install -e .[docs] + +# Build documentation +invoke docs.build + +# View documentation +invoke docs.show +``` + +## Architecture + +### Component Overview + +- **Parser**: Base class for parsing boolean conditional expressions +- **SQLAParser**: Specialized parser for SQLAlchemy filter conversion +- **Clauses**: pyparsing grammar definitions (`condition`, `between_cond`, `words`) +- **Actions**: Classes controlling parse behavior (`Condition`, `Word`, `SQLACondition`) +- **Booleans**: Logic handlers (`BoolNot`, `BoolAnd`, `BoolOr`) +- **Mixins**: `SQLAMixin` provides SQLAlchemy filter conversion + +### Parsing Flow + +1. **Input String** → Parser receives conditional expression +2. **Grammar Matching** → pyparsing matches clauses (condition, between, etc.) +3. **Parse Actions** → Actions convert tokens to objects +4. **Boolean Logic** → `infixNotation` handles NOT→AND→OR precedence +5. **Output Object** → Returns structured result with `.params` and `.conditions` + +## Contributing + +Contributions are welcome! Please feel free to submit a Pull Request. + +## License + +BSD 3-Clause License. See [LICENSE.md](LICENSE.md) for details. + +## Links + +- **Documentation**: https://boolean-parser.readthedocs.io/en/latest/ +- **Repository**: https://github.com/havok2063/boolean_parser +- **Issue Tracker**: https://github.com/havok2063/boolean_parser/issues