Metadata-Version: 2.4
Name: poridhiweb
Version: 0.0.5
Summary: PoridhiFrame Python Web Framework built for learning purposes.
Author: Dipanjal Maitra
Author-email: dipanjalmaitra@gmail.com
License: MIT
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Application
Requires-Python: >=3.6.0
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: Jinja2==3.1.6
Requires-Dist: parse==1.20.2
Requires-Dist: WebOb==1.8.9
Requires-Dist: whitenoise==6.11.0
Dynamic: author
Dynamic: author-email
Dynamic: classifier
Dynamic: description
Dynamic: description-content-type
Dynamic: license
Dynamic: license-file
Dynamic: requires-dist
Dynamic: requires-python
Dynamic: summary


# PoridhiWeb

PoridhiWeb is a small, educational WSGI-compatible Python web framework built from scratch to help you understand how web frameworks work. It's intentionally lightweight and opinionated for learning and experimentation.

## Features
- **WSGI Compatible**: The framework exposes a callable application usable with any WSGI server.
- **Routing**: Supports both automatic (path pattern) and explicit route registration.
- **Handlers**: Function-based and class-based handlers are supported.
- **Middlewares**: Compose request/response processing via middleware classes. Includes `ErrorHandlerMiddleware` and helpers.
- **Templating**: Provides a templating system accessible via the `template()` helper on the app.
- **Static Files**: Static files (CSS/JS/images) can be served from a `static/` directory.
- **Error Handling**: Built-in `ResponseError` and optional middleware to convert exceptions into JSON responses.
- **HTTP Method Control**: Route definitions can restrict allowed HTTP methods.
- **ORM**: Lightweight Object-Relational Mapping with SQLite support, featuring model definitions, CRUD operations, and foreign key relationships.
- **Published**: The package is available on PyPI for easy installation.

## Installation
- **From PyPI**: `pip install poridhiweb`

## Quick Start

- **Create a minimal app**

```python
from poridhiweb.framework import PoridhiFrame
app = PoridhiFrame()

@app.route('/')
def index(request):
    return app.templates_env.get_template('dashboard.html').render()

if __name__ == '__main__':
    from wsgiref.simple_server import make_server
    server = make_server('0.0.0.0', 8080, app)
    print('Serving on http://0.0.0.0:8080')
    server.serve_forever()
```

## Function-based handler

```python
from poridhiweb.framework import PoridhiFrame
from poridhiweb.models.responses import JSONResponse

app = PoridhiFrame()

@app.route('/hello')
def hello(request):
    return JSONResponse({"message": "Hello from PoridhiWeb"})
```

## Class-based Handler
Poridhiweb support both automatic and manually registered Class Based handlers

### Self registered Class-based Handler
The function names should matched with HTTP request method.
```python
@app.route('/items')
class ItemHandler:
    def __init__(self):
        self.service = ItemService()

    # get all products
    def get(self, request):
        items: list[dict] = self.service.get_items()
        return JSONResponse(items)
    
    # create a product
    def post(self, request):
        items: dict = self.service.create_items()
        return JSONResponse(items)
```
Notes:
- Self registered Class-based handlers are registered as classes. The framework will instantiate the class and call the method matching the HTTP method name (e.g., `get`, `post`).


### Manually registerd Class-based Handler
If you need both custom handlers in a class then you can register routes manually

```python
from poridhiweb.framework import PoridhiFrame
from poridhiweb.models.responses import JSONResponse

app = PoridhiFrame()

class ItemHandlerCustomRouting:
    def get_by_id(self, request, item_id=None):
        return JSONResponse({"item_id": item_id})
    
    def get_by_category(self, request, category=None):
        # JSONResponse also supports list of classes
        items: list[Item] = items_service.get_by_category()
        return JSONResponse(items)

handler = ItemHandlerCustomRouting()
app.add_route('/items/{item_id:d}', handler.get_by_id)
app.add_route('/items/{category}', handler.get_by_category)
```

## Routing & Path Variables
- Paths can include variables using the `{name}` syntax. The framework will parse them and pass as kwargs to your handler.
- Example: `app.add_route('/users/{user_id}', handler)` — handler will receive `user_id` as a keyword argument.

## Middlewares & Error Handling
- **Built-in middlewares**: `ErrorHandlerMiddleware`, `ReqResLoggingMiddleware`, and `ExecutionTimeMiddleware` are provided in `poridhiweb.middlewares` package.
- **ResponseError**: Raise `poridhiweb.exceptions.ResponseError` (or its subclasses) from handlers to return structured JSON error responses. The `ErrorHandlerMiddleware` converts `ResponseError` into an appropriate JSON response with the specified HTTP status.

Example — adding middleware and a simple error:

```python
from poridhiweb.framework import PoridhiFrame
from poridhiweb.middlewares import ErrorHandlerMiddleware
from poridhiweb.exceptions import ResponseError

app = PoridhiFrame()
app.add_middleware(ErrorHandlerMiddleware)

@app.route('/fail')
def fail(request):
    raise ResponseError('This is a custom error', 400)
```

- **Response JSON**
```json
{
    "message": "This is a custom error"
}
```

## Templating
- The app provides a templating system. Templates are loaded from the `templates` directory by default. Use `app.template(template_name, context)` to generate view from a template dynamically.

Example:
Register the template if you have a customer template directory

```python
from poridhiweb.framework import PoridhiFrame
from poridhiweb.models.responses import HTMLResponse

app = PoridhiFrame(template_dir=f"{cwd}/templates")

@app.route('/dashboard')
def dashboard(request) -> Response:
    name = "Hello User"
    title = "Dashboard View"
    html_content = app.template(
        "dashboard.html",
        context={"name": name, "title": title}
    )
    return HTMLResponse(html_content)
```

**Static Files**
- Static assets under the `static` directory are served automatically. Place CSS/JS/images in `static/` and reference them from your templates.


## ORM (Object-Relational Mapping)

PoridhiWeb includes a lightweight ORM for database interactions. Currently supports SQLite with a factory pattern for future database dialect support.

### Database Connection

Use `DatabaseFactory` to create a database connection:

```python
from poridhiweb.orm.db_factory import DatabaseFactory, Dialect

db = DatabaseFactory(dialect=Dialect.SQLITE).get_connection("mydb.sqlite")
```

### Defining Models

Define your models by extending the `Table` class and using column types:

```python
from poridhiweb.orm.table import Table
from poridhiweb.orm.column import Column, PrimaryKey, ForeignKey

class Author(Table):
    id = PrimaryKey(int, auto_increment=True)
    name = Column(str)
    age = Column(int)

class Book(Table):
    id = PrimaryKey(int, auto_increment=True)
    title = Column(str)
    author = ForeignKey(Author)  # Foreign key relationship
```

### Supported Column Types

| Python Type | SQLite Type |
|-------------|-------------|
| `int`       | INTEGER     |
| `str`       | TEXT        |
| `float`     | REAL        |
| `bool`      | INTEGER     |
| `bytes`     | BLOB        |

### CRUD Operations

**Create Tables**
```python
db.create(Author)
db.create(Book)
```

**Insert Records**
```python
author = Author(name="John Doe", age=30)
db.save(author)
print(author.id)  # Auto-generated ID is set after save
```

**Query Records**
```python
# Get all records
authors = db.get_all(Author)

# Get by ID
author = db.get_by_id(Author, id=1)
```

**Update Records**
```python
author = db.get_by_id(Author, id=1)
author.name = "Jane Doe"
db.update(author)
```

**Delete Records**
```python
db.delete(Author, id=1)
```

### Foreign Key Relationships

Foreign keys are automatically resolved when querying:

```python
# Create and save an author
author = Author(name="Jane Doe", age=28)
db.save(author)

# Create a book with author reference
book = Book(title="My First Book", author=author)
db.save(book)

# When fetching, foreign key is automatically loaded
fetched_book = db.get_by_id(Book, id=1)
print(fetched_book.author.name)  # "Jane Doe"
```

### Exception Handling

The ORM provides a `RecordNotFound` exception for missing records:

```python
from poridhiweb.orm.exceptions import RecordNotFound

try:
    author = db.get_by_id(Author, id=999)
except RecordNotFound as e:
    print(f"Error: {e}")
```

**WSGI Compatibility**
- The `PoridhiFrame` instance is a valid WSGI application. You can run it with any WSGI server (uWSGI, Gunicorn, or `wsgiref` during development).

You can run the demo application with Gunicorn service using
```
make run
```

**Tests & Examples**
- See the `tests/` and `demo_app/` directories in this repository for usage examples and test coverage.

**Contributing**
- This project is intended for learning. Contributions that improve docs, add examples, or clarify internals are welcome.

**License**
- See the `LICENSE` file in this repository.
