# Django Swifty

A powerful, lightweight Django toolkit that enhances your development experience with authentication, caching, structured logging, dynamic serializers, and expression evaluation — all with zero platform-specific dependencies.

## Installation

```bash
pip install django-swifty
```

**With Redis caching support:**

```bash
pip install django-swifty[redis]
```

**With all optional features:**

```bash
pip install django-swifty[all]
```

## Quick Start

Add `swifty` to your `INSTALLED_APPS`:

```python
INSTALLED_APPS = [
    ...
    "swifty",
]
```

## Features

### 🔐 Authentication & Authorization

JWT-based authentication with layered permissions via Django REST Framework.

```python
from swifty.auth.permissions import SwiftyPermission
from swifty.viewsets.viewsets import SwiftyViewSet

class AdminPermission(SwiftyPermission):
    """Only allow users with admin or superuser role."""
    permission_layers = [
        {"path": "role", "allowed": ["admin", "superuser"]},
    ]

class MyViewSet(SwiftyViewSet):
    permission_classes = (AdminPermission,)

    def list(self, request):
        return Response({"items": []})
```

**Features:**
- JWT token rotation and validation via `AccessTokenManager`
- Layer-based permission checks with dynamic callables
- Per-action permission and authentication class overrides

---

### ⚡ Caching

Method-level caching with pluggable backends (Redis by default).

```python
from swifty.cache.manager import cache, cache_property

class ProductService:
    redis_cache_pattern = {"prefix": "PRODUCTS", "ttl": 3600, "vary_by": ["user_id"]}

    @cache(ttl=1800, static_key="featured")
    def get_featured(self):
        return Product.objects.filter(featured=True)

    @cache_property
    def categories(self):
        return Category.objects.all()
```

**Features:**
- `@cache` decorator for methods with TTL, static keys, and vary-by
- `@cache_property` for cached properties
- Configurable cache backend via `settings.CACHE_BACKEND`
- Auto-reconnection on Redis failures
- Pipeline-based batch cache deletion

**Configuration:**

```python
# settings.py
import redis

REDIS_CONNECTION_POOL = {
    "pool_1": redis.ConnectionPool(host="localhost", port=6379, db=1),
}

# Optional: custom cache backend
# CACHE_BACKEND = "myapp.cache.MyCustomCache"
```

---

### 📝 Structured Logging

Structured logging via `structlog` with automatic request context binding.

```python
from swifty.logging.logger import SwiftyLoggerMixin

class OrderService(SwiftyLoggerMixin):
    def create_order(self, data):
        self.logger.info("Creating order", item_count=len(data["items"]))
        # ... business logic ...
        self.logger.info("Order created", order_id=order.id)
```

**Features:**
- `SwiftyLoggerMixin` provides a cached `self.logger` property
- Automatic `module.ClassName` logger naming
- Request context binding (user, path, method) via `RequestContext`
- JSON, console, and key-value formatters

**Configuration:**

```python
# settings.py
# Swifty auto-configures logging if LOGGING is not set.
# Customize via:
SWIFTY_LOGGING_CONFIG = {
    "log_level": logging.DEBUG,
}

# Custom context fields:
CONTEXT_FIELDS_MAPPING = (
    ("user.username", "username"),
    ("user.id", "user_id"),
    ("method", "method"),
    ("path", "path"),
)
```

---

### 🔄 Dynamic Serializers

Build serializers dynamically from configuration with expression-based calculators and validators.

```python
from swifty.serializers.manager import create_dynamic_serializer

fields_config = [
    {"field_name": "name", "type": "CharField", "required": True, "max_length": 100},
    {"field_name": "price", "type": "FloatField", "min_value": 0},
    {"field_name": "quantity", "type": "IntegerField", "default": 1},
    {
        "field_name": "total",
        "type": "FloatField",
        "calculator": {"expression": "price * quantity"},
    },
]

MySerializer = create_dynamic_serializer(fields_config)
```

**Available field types:**
`CharField`, `EmailField`, `URLField`, `UUIDField`, `IntegerField`, `FloatField`, `DecimalField`, `BooleanField`, `DateField`, `DateTimeField`, `DateRangeField`, `TimeField`, `DurationField`, `ListField`, `DictField`, `ChoiceField`, `MultipleChoiceField`, `SlugField`, `FileField`, `ImageField`, `JSONField`, `ReadOnlyField`, `HiddenField`, `SectionField`, `SectionManyField`

---

### 🧮 Expression Evaluator

Safe mathematical and logical expression evaluation with AST-based security.

```python
from swifty.expressionator.manager import ExpressionatorManager

em = ExpressionatorManager(data={"price": 100, "tax_rate": 0.1, "quantity": 3})

total = em.evaluate("price * quantity * (1 + tax_rate)")  # 330.0
is_bulk = em.evaluate("quantity > 5")  # False
discount = em.evaluate("max(price * 0.1, 10)")  # 10.0
```

**Features:**
- AST node whitelisting for security (no arbitrary code execution)
- Math functions (`sin`, `cos`, `sqrt`, `pi`, etc.)
- Safe builtins (`max`, `min`, `abs`, `len`, `sum`, `round`, etc.)
- Expression compilation caching for performance
- Extensible via custom namespaces

```python
from swifty.expressionator.namespace import ExpressionatorNamespace

class MyNamespace(ExpressionatorNamespace):
    def _expr_clamp(self, value, low, high):
        return max(low, min(high, value))

em = ExpressionatorManager(data={"x": 150}, namespace=MyNamespace)
em.evaluate("clamp(x, 0, 100)")  # 100
```

---

### 🛠 Utilities

**Path-based data access:**

```python
from swifty.utils.mapper import getpath

data = {"user": {"profile": {"name": "Alice"}}}
getpath(data, "user.profile.name")  # "Alice"
getpath(data, "user.missing", "default")  # "default"
```

**Dict-to-object conversion:**

```python
from swifty.utils.objects import DictObject

obj = DictObject({"user": {"name": "Alice", "tags": ["admin", "staff"]}})
obj.user.name  # "Alice"
obj.user.tags  # DotList(["admin", "staff"])
```

**Once-trigger decorator:**

```python
from swifty.utils.decorators import once_trigger

class ExpensiveService:
    @property
    @once_trigger
    def connection(self):
        return create_expensive_connection()  # Called only once per instance
```

**Dynamic module importing:**

```python
from swifty.utils.importer import import_module_attribute

MyClass = import_module_attribute("myapp.module.MyClass")
```

---

### 🌐 ViewSets

Enhanced DRF ViewSets with JWT auth, structured logging, and request context.

```python
from swifty.viewsets.viewsets import SwiftyViewSet
from swifty.viewsets.exceptions import JsonException
from rest_framework.response import Response

class ProductViewSet(SwiftyViewSet):
    def list(self, request):
        self.logger.info("Listing products")
        return Response({"products": []})

    def create(self, request):
        if not request.data.get("name"):
            raise JsonException({"name": "This field is required."}, status_code=400)
        return Response({"created": True}, status=201)
```

**Features:**
- Auto request context binding to logger
- Per-action authentication and permission overrides
- `JsonException` for structured JSON error responses
- Double-initialization guard for request objects

## Requirements

- Python ≥ 3.10
- Django ≥ 3.2
- Django REST Framework ≥ 3.12

## Configuration Reference

| Setting | Description | Default |
|---------|-------------|---------|
| `REDIS_CONNECTION_POOL` | Dict of Redis connection pools | Required for Redis cache |
| `CACHE_BACKEND` | Custom cache backend class path | `swifty.redis.cache.RedisCache` |
| `SWIFTY_LOGGING_CONFIG` | Kwargs for `get_logging_config()` | `{}` |
| `CONTEXT_FIELDS_MAPPING` | Request context field mappings | See logging section |

## Contributing

We welcome contributions! Here's how you can help:

1. Fork the repository
2. Create your feature branch (`git checkout -b feature/AmazingFeature`)
3. Commit your changes (`git commit -m 'Add some AmazingFeature'`)
4. Push to the branch (`git push origin feature/AmazingFeature`)
5. Open a Pull Request

## License

This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.

## Authors

- **Phuc Le** - _Initial work_ - [Github](https://github.com/hphuc3005)

---

Made with ❤️ by the Django Swifty Team
