Metadata-Version: 2.4
Name: guardnet
Version: 0.1.1
Summary: 
Author: cvaz1306
Author-email: christophervaz160@gmail.com
Requires-Python: >=3.11
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Requires-Dist: pydantic (>=2.12.5,<3.0.0)
Description-Content-Type: text/markdown

# 🛡️ GuardNet

**GuardNet** is a graph-based, type-safe ReBAC (Relationship-Based Access Control) engine for Python 3.11+. 

Inspired by distributed state machine patterns, GuardNet allows you to model complex authorization logic as a directed graph of Python classes. It solves the "spaghetti if-statement" problem in authorization by using recursive delegation, short-circuiting logical gates, and request-scoped memoization.

---

## ✨ Key Features

- **Code as Truth**: Policies are standard Python classes. No custom DSLs, JSON, or YAML logic.
- **Context Awareness**: Use Pydantic models to define exactly what data is needed for a specific check.
- **Relationship-Based (ReBAC)**: Easily model inheritance (e.g., Document -> Folder -> Organization) using the `Delegate` pattern.
- **Memoization**: Identical checks within the same request context are executed only once, saving database overhead.
- **Short-Circuiting**: `AnyOf` (OR) and `AllOf` (AND) gates stop execution as soon as a result is determined.
- **Static Validation**: Detect circular dependencies and connectivity issues in your authorization graph before you deploy.

---

## 🚀 Quick Start

### 1. Define your Context and Nodes
Nodes are the building blocks of your policy. They receive a typed context and return a `Decision`.

```python
from pydantic import BaseModel
from guardnet import GuardNode, Effect, AnyOf, PolicyEngine

class AuthCtx(BaseModel):
    user_id: str
    resource_id: str

class IsAdmin(GuardNode[AuthCtx]):
    async def evaluate(self, ctx: AuthCtx):
        # Direct boolean logic
        return Effect.ALLOW if ctx.user_id == "admin-1" else Effect.DENY

class IsOwner(GuardNode[AuthCtx]):
    async def evaluate(self, ctx: AuthCtx):
        # You can perform async DB calls here
        return Effect.ALLOW if ctx.resource_id == "owner-res" else Effect.DENY

class RootPolicy(GuardNode[AuthCtx]):
    async def evaluate(self, ctx: AuthCtx):
        # Logical composition with short-circuiting
        return AnyOf(branches=[IsAdmin, IsOwner])
```

### 2. Execute the Check
The `PolicyEngine` manages the evaluation and handles request-scoped caching.

```python
engine = PolicyEngine(nodes=[RootPolicy, IsAdmin, IsOwner])

allowed = await engine.check(
    start_node=RootPolicy, 
    context=AuthCtx(user_id="user-1", resource_id="owner-res")
)

if allowed:
    print("Access Granted!")
```

---

## 🛠️ Advanced: ReBAC and Inheritance

GuardNet shines when resources inherit permissions from parents. Use `Delegate` to switch contexts mid-evaluation.

```python
class CheckDocument(GuardNode[DocCtx]):
    async def evaluate(self, ctx: DocCtx):
        # Look up the parent folder ID
        parent_id = await db.get_parent_folder(ctx.doc_id)
        
        # Delegate checking to the Folder policy with a new context
        return Delegate(
            node_cls=CheckFolder,
            sub_context=FolderCtx(user_id=ctx.user_id, folder_id=parent_id)
        )
```

---

## 🔍 Static Analysis

Prevent infinite loops or broken logic in your security graph by validating it at startup or in CI/CD.

```python
from guardnet import GraphAnalyzer

# This will raise a RecursionError if a cycle is found
GraphAnalyzer.validate(RootPolicy, engine._registry)
```

## ⚖️ License

MIT

