...
This commit is contained in:
		
							
								
								
									
										131
									
								
								herodb/src/models/py/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										131
									
								
								herodb/src/models/py/README.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,131 @@
 | 
			
		||||
# Business Models Python Port
 | 
			
		||||
 | 
			
		||||
This directory contains a Python port of the business models from the Rust codebase, using SQLModel for database integration.
 | 
			
		||||
 | 
			
		||||
## Overview
 | 
			
		||||
 | 
			
		||||
This project includes:
 | 
			
		||||
 | 
			
		||||
1. Python port of Rust business models using SQLModel
 | 
			
		||||
2. FastAPI server with OpenAPI/Swagger documentation
 | 
			
		||||
3. CRUD operations for all models
 | 
			
		||||
4. Convenience endpoints for common operations
 | 
			
		||||
 | 
			
		||||
The models ported from Rust to Python include:
 | 
			
		||||
 | 
			
		||||
- **Currency**: Represents a monetary value with amount and currency code
 | 
			
		||||
- **Customer**: Represents a customer who can purchase products or services
 | 
			
		||||
- **Product**: Represents a product or service offered
 | 
			
		||||
- **ProductComponent**: Represents a component of a product
 | 
			
		||||
- **SaleItem**: Represents an item in a sale
 | 
			
		||||
- **Sale**: Represents a sale of products or services
 | 
			
		||||
 | 
			
		||||
## Structure
 | 
			
		||||
 | 
			
		||||
- `models.py`: Contains the SQLModel definitions for all business models
 | 
			
		||||
- `example.py`: Demonstrates how to use the models with a sample application
 | 
			
		||||
- `install_and_run.sh`: Bash script to install dependencies using `uv` and run the example
 | 
			
		||||
- `api.py`: FastAPI server providing CRUD operations for all models
 | 
			
		||||
- `server.sh`: Bash script to start the FastAPI server
 | 
			
		||||
 | 
			
		||||
## Requirements
 | 
			
		||||
 | 
			
		||||
- Python 3.7+
 | 
			
		||||
- [uv](https://github.com/astral-sh/uv) for dependency management
 | 
			
		||||
 | 
			
		||||
## Installation
 | 
			
		||||
 | 
			
		||||
The project uses `uv` for dependency management. To install dependencies and run the example:
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
./install_and_run.sh
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## API Server
 | 
			
		||||
 | 
			
		||||
The project includes a FastAPI server that provides CRUD operations for all models and some convenience endpoints.
 | 
			
		||||
 | 
			
		||||
### Starting the Server
 | 
			
		||||
 | 
			
		||||
To start the API server:
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
./server.sh
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
This script will:
 | 
			
		||||
1. Create a virtual environment if it doesn't exist
 | 
			
		||||
2. Install the required dependencies using `uv`
 | 
			
		||||
3. Start the FastAPI server with hot reloading enabled
 | 
			
		||||
 | 
			
		||||
### API Documentation
 | 
			
		||||
 | 
			
		||||
Once the server is running, you can access the OpenAPI documentation at:
 | 
			
		||||
 | 
			
		||||
- Swagger UI: http://localhost:8000/docs
 | 
			
		||||
- ReDoc: http://localhost:8000/redoc
 | 
			
		||||
 | 
			
		||||
### Available Endpoints
 | 
			
		||||
 | 
			
		||||
The API provides the following endpoints:
 | 
			
		||||
 | 
			
		||||
#### Currencies
 | 
			
		||||
- `GET /currencies/`: List all currencies
 | 
			
		||||
- `POST /currencies/`: Create a new currency
 | 
			
		||||
- `GET /currencies/{currency_id}`: Get a specific currency
 | 
			
		||||
- `PUT /currencies/{currency_id}`: Update a currency
 | 
			
		||||
- `DELETE /currencies/{currency_id}`: Delete a currency
 | 
			
		||||
 | 
			
		||||
#### Customers
 | 
			
		||||
- `GET /customers/`: List all customers
 | 
			
		||||
- `POST /customers/`: Create a new customer
 | 
			
		||||
- `GET /customers/{customer_id}`: Get a specific customer
 | 
			
		||||
- `PUT /customers/{customer_id}`: Update a customer
 | 
			
		||||
- `DELETE /customers/{customer_id}`: Delete a customer
 | 
			
		||||
- `GET /customers/{customer_id}/sales/`: Get all sales for a customer
 | 
			
		||||
 | 
			
		||||
#### Products
 | 
			
		||||
- `GET /products/`: List all products
 | 
			
		||||
- `POST /products/`: Create a new product
 | 
			
		||||
- `GET /products/{product_id}`: Get a specific product
 | 
			
		||||
- `PUT /products/{product_id}`: Update a product
 | 
			
		||||
- `DELETE /products/{product_id}`: Delete a product
 | 
			
		||||
- `GET /products/available/`: Get all available products
 | 
			
		||||
- `POST /products/{product_id}/components/`: Add a component to a product
 | 
			
		||||
- `GET /products/{product_id}/components/`: Get all components for a product
 | 
			
		||||
 | 
			
		||||
#### Sales
 | 
			
		||||
- `GET /sales/`: List all sales
 | 
			
		||||
- `POST /sales/`: Create a new sale
 | 
			
		||||
- `GET /sales/{sale_id}`: Get a specific sale
 | 
			
		||||
- `PUT /sales/{sale_id}`: Update a sale
 | 
			
		||||
- `DELETE /sales/{sale_id}`: Delete a sale
 | 
			
		||||
- `PUT /sales/{sale_id}/status/{status}`: Update the status of a sale
 | 
			
		||||
- `POST /sales/{sale_id}/items/`: Add an item to a sale
 | 
			
		||||
- `GET /sales/{sale_id}/items/`: Get all items for a sale
 | 
			
		||||
 | 
			
		||||
## Dependencies
 | 
			
		||||
 | 
			
		||||
- SQLModel: For database models and ORM functionality
 | 
			
		||||
- Pydantic: For data validation (used by SQLModel)
 | 
			
		||||
- FastAPI: For creating the API server
 | 
			
		||||
- Uvicorn: ASGI server for running FastAPI applications
 | 
			
		||||
 | 
			
		||||
## Example Usage
 | 
			
		||||
 | 
			
		||||
The `example.py` script demonstrates:
 | 
			
		||||
 | 
			
		||||
1. Creating an SQLite database
 | 
			
		||||
2. Defining and creating tables for the models
 | 
			
		||||
3. Creating sample data (customers, products, sales)
 | 
			
		||||
4. Performing operations on the data
 | 
			
		||||
5. Querying and displaying the data
 | 
			
		||||
 | 
			
		||||
To run the example manually (after activating the virtual environment):
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
# From the py directory
 | 
			
		||||
python example.py
 | 
			
		||||
 | 
			
		||||
# Or from the parent directory
 | 
			
		||||
cd py && python example.py
 | 
			
		||||
							
								
								
									
										3
									
								
								herodb/src/models/py/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								herodb/src/models/py/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
			
		||||
"""
 | 
			
		||||
Python port of the business models from Rust.
 | 
			
		||||
"""
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								herodb/src/models/py/__pycache__/api.cpython-312.pyc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								herodb/src/models/py/__pycache__/api.cpython-312.pyc
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								herodb/src/models/py/__pycache__/models.cpython-312.pyc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								herodb/src/models/py/__pycache__/models.cpython-312.pyc
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										455
									
								
								herodb/src/models/py/api.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										455
									
								
								herodb/src/models/py/api.py
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,455 @@
 | 
			
		||||
#!/usr/bin/env python3
 | 
			
		||||
"""
 | 
			
		||||
FastAPI server providing CRUD operations for business models.
 | 
			
		||||
"""
 | 
			
		||||
import os
 | 
			
		||||
from datetime import datetime
 | 
			
		||||
from typing import List, Optional
 | 
			
		||||
 | 
			
		||||
from fastapi import Depends, FastAPI, HTTPException, Query
 | 
			
		||||
from fastapi.middleware.cors import CORSMiddleware
 | 
			
		||||
from sqlmodel import Session, SQLModel, create_engine, select
 | 
			
		||||
 | 
			
		||||
from models import (
 | 
			
		||||
    Currency,
 | 
			
		||||
    Customer,
 | 
			
		||||
    Product,
 | 
			
		||||
    ProductComponent,
 | 
			
		||||
    ProductStatus,
 | 
			
		||||
    ProductType,
 | 
			
		||||
    Sale,
 | 
			
		||||
    SaleItem,
 | 
			
		||||
    SaleStatus,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
# Create database
 | 
			
		||||
DATABASE_URL = os.environ.get("DATABASE_URL", "sqlite:///business.db")
 | 
			
		||||
engine = create_engine(DATABASE_URL, echo=False)
 | 
			
		||||
 | 
			
		||||
# Create tables
 | 
			
		||||
SQLModel.metadata.create_all(engine)
 | 
			
		||||
 | 
			
		||||
# Create FastAPI app
 | 
			
		||||
app = FastAPI(
 | 
			
		||||
    title="Business API",
 | 
			
		||||
    description="API for business models",
 | 
			
		||||
    version="1.0.0",
 | 
			
		||||
    docs_url="/docs",
 | 
			
		||||
    redoc_url="/redoc",
 | 
			
		||||
    openapi_url="/openapi.json",
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
# Add CORS middleware
 | 
			
		||||
app.add_middleware(
 | 
			
		||||
    CORSMiddleware,
 | 
			
		||||
    allow_origins=["*"],
 | 
			
		||||
    allow_credentials=True,
 | 
			
		||||
    allow_methods=["*"],
 | 
			
		||||
    allow_headers=["*"],
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
# Dependency to get database session
 | 
			
		||||
def get_session():
 | 
			
		||||
    with Session(engine) as session:
 | 
			
		||||
        yield session
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Root endpoint
 | 
			
		||||
@app.get("/")
 | 
			
		||||
async def root():
 | 
			
		||||
    return {"message": "Welcome to the Business API"}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Currency endpoints
 | 
			
		||||
@app.post("/currencies/", response_model=Currency, tags=["Currencies"])
 | 
			
		||||
def create_currency(currency: Currency, session: Session = Depends(get_session)):
 | 
			
		||||
    """Create a new currency"""
 | 
			
		||||
    session.add(currency)
 | 
			
		||||
    session.commit()
 | 
			
		||||
    session.refresh(currency)
 | 
			
		||||
    return currency
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@app.get("/currencies/", response_model=List[Currency], tags=["Currencies"])
 | 
			
		||||
def read_currencies(
 | 
			
		||||
    skip: int = 0, limit: int = 100, session: Session = Depends(get_session)
 | 
			
		||||
):
 | 
			
		||||
    """Get all currencies"""
 | 
			
		||||
    currencies = session.exec(select(Currency).offset(skip).limit(limit)).all()
 | 
			
		||||
    return currencies
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@app.get("/currencies/{currency_id}", response_model=Currency, tags=["Currencies"])
 | 
			
		||||
def read_currency(currency_id: int, session: Session = Depends(get_session)):
 | 
			
		||||
    """Get a currency by ID"""
 | 
			
		||||
    currency = session.get(Currency, currency_id)
 | 
			
		||||
    if not currency:
 | 
			
		||||
        raise HTTPException(status_code=404, detail="Currency not found")
 | 
			
		||||
    return currency
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@app.put("/currencies/{currency_id}", response_model=Currency, tags=["Currencies"])
 | 
			
		||||
def update_currency(
 | 
			
		||||
    currency_id: int, currency_data: Currency, session: Session = Depends(get_session)
 | 
			
		||||
):
 | 
			
		||||
    """Update a currency"""
 | 
			
		||||
    currency = session.get(Currency, currency_id)
 | 
			
		||||
    if not currency:
 | 
			
		||||
        raise HTTPException(status_code=404, detail="Currency not found")
 | 
			
		||||
    
 | 
			
		||||
    # Update currency attributes
 | 
			
		||||
    currency_data_dict = currency_data.dict(exclude_unset=True)
 | 
			
		||||
    for key, value in currency_data_dict.items():
 | 
			
		||||
        setattr(currency, key, value)
 | 
			
		||||
    
 | 
			
		||||
    session.add(currency)
 | 
			
		||||
    session.commit()
 | 
			
		||||
    session.refresh(currency)
 | 
			
		||||
    return currency
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@app.delete("/currencies/{currency_id}", tags=["Currencies"])
 | 
			
		||||
def delete_currency(currency_id: int, session: Session = Depends(get_session)):
 | 
			
		||||
    """Delete a currency"""
 | 
			
		||||
    currency = session.get(Currency, currency_id)
 | 
			
		||||
    if not currency:
 | 
			
		||||
        raise HTTPException(status_code=404, detail="Currency not found")
 | 
			
		||||
    
 | 
			
		||||
    session.delete(currency)
 | 
			
		||||
    session.commit()
 | 
			
		||||
    return {"message": "Currency deleted successfully"}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Customer endpoints
 | 
			
		||||
@app.post("/customers/", response_model=Customer, tags=["Customers"])
 | 
			
		||||
def create_customer(customer: Customer, session: Session = Depends(get_session)):
 | 
			
		||||
    """Create a new customer"""
 | 
			
		||||
    session.add(customer)
 | 
			
		||||
    session.commit()
 | 
			
		||||
    session.refresh(customer)
 | 
			
		||||
    return customer
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@app.get("/customers/", response_model=List[Customer], tags=["Customers"])
 | 
			
		||||
def read_customers(
 | 
			
		||||
    skip: int = 0, limit: int = 100, session: Session = Depends(get_session)
 | 
			
		||||
):
 | 
			
		||||
    """Get all customers"""
 | 
			
		||||
    customers = session.exec(select(Customer).offset(skip).limit(limit)).all()
 | 
			
		||||
    return customers
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@app.get("/customers/{customer_id}", response_model=Customer, tags=["Customers"])
 | 
			
		||||
def read_customer(customer_id: int, session: Session = Depends(get_session)):
 | 
			
		||||
    """Get a customer by ID"""
 | 
			
		||||
    customer = session.get(Customer, customer_id)
 | 
			
		||||
    if not customer:
 | 
			
		||||
        raise HTTPException(status_code=404, detail="Customer not found")
 | 
			
		||||
    return customer
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@app.put("/customers/{customer_id}", response_model=Customer, tags=["Customers"])
 | 
			
		||||
def update_customer(
 | 
			
		||||
    customer_id: int, customer_data: Customer, session: Session = Depends(get_session)
 | 
			
		||||
):
 | 
			
		||||
    """Update a customer"""
 | 
			
		||||
    customer = session.get(Customer, customer_id)
 | 
			
		||||
    if not customer:
 | 
			
		||||
        raise HTTPException(status_code=404, detail="Customer not found")
 | 
			
		||||
    
 | 
			
		||||
    # Update customer attributes
 | 
			
		||||
    customer_data_dict = customer_data.dict(exclude_unset=True)
 | 
			
		||||
    for key, value in customer_data_dict.items():
 | 
			
		||||
        setattr(customer, key, value)
 | 
			
		||||
    
 | 
			
		||||
    session.add(customer)
 | 
			
		||||
    session.commit()
 | 
			
		||||
    session.refresh(customer)
 | 
			
		||||
    return customer
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@app.delete("/customers/{customer_id}", tags=["Customers"])
 | 
			
		||||
def delete_customer(customer_id: int, session: Session = Depends(get_session)):
 | 
			
		||||
    """Delete a customer"""
 | 
			
		||||
    customer = session.get(Customer, customer_id)
 | 
			
		||||
    if not customer:
 | 
			
		||||
        raise HTTPException(status_code=404, detail="Customer not found")
 | 
			
		||||
    
 | 
			
		||||
    session.delete(customer)
 | 
			
		||||
    session.commit()
 | 
			
		||||
    return {"message": "Customer deleted successfully"}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Product endpoints
 | 
			
		||||
@app.post("/products/", response_model=Product, tags=["Products"])
 | 
			
		||||
def create_product(product: Product, session: Session = Depends(get_session)):
 | 
			
		||||
    """Create a new product"""
 | 
			
		||||
    session.add(product)
 | 
			
		||||
    session.commit()
 | 
			
		||||
    session.refresh(product)
 | 
			
		||||
    return product
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@app.get("/products/", response_model=List[Product], tags=["Products"])
 | 
			
		||||
def read_products(
 | 
			
		||||
    skip: int = 0,
 | 
			
		||||
    limit: int = 100,
 | 
			
		||||
    category: Optional[str] = None,
 | 
			
		||||
    status: Optional[ProductStatus] = None,
 | 
			
		||||
    istemplate: Optional[bool] = None,
 | 
			
		||||
    session: Session = Depends(get_session)
 | 
			
		||||
):
 | 
			
		||||
    """Get all products with optional filtering"""
 | 
			
		||||
    query = select(Product)
 | 
			
		||||
    
 | 
			
		||||
    if category:
 | 
			
		||||
        query = query.where(Product.category == category)
 | 
			
		||||
    
 | 
			
		||||
    if status:
 | 
			
		||||
        query = query.where(Product.status == status)
 | 
			
		||||
    
 | 
			
		||||
    if istemplate is not None:
 | 
			
		||||
        query = query.where(Product.istemplate == istemplate)
 | 
			
		||||
    
 | 
			
		||||
    products = session.exec(query.offset(skip).limit(limit)).all()
 | 
			
		||||
    return products
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@app.get("/products/{product_id}", response_model=Product, tags=["Products"])
 | 
			
		||||
def read_product(product_id: int, session: Session = Depends(get_session)):
 | 
			
		||||
    """Get a product by ID"""
 | 
			
		||||
    product = session.get(Product, product_id)
 | 
			
		||||
    if not product:
 | 
			
		||||
        raise HTTPException(status_code=404, detail="Product not found")
 | 
			
		||||
    return product
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@app.put("/products/{product_id}", response_model=Product, tags=["Products"])
 | 
			
		||||
def update_product(
 | 
			
		||||
    product_id: int, product_data: Product, session: Session = Depends(get_session)
 | 
			
		||||
):
 | 
			
		||||
    """Update a product"""
 | 
			
		||||
    product = session.get(Product, product_id)
 | 
			
		||||
    if not product:
 | 
			
		||||
        raise HTTPException(status_code=404, detail="Product not found")
 | 
			
		||||
    
 | 
			
		||||
    # Update product attributes
 | 
			
		||||
    product_data_dict = product_data.dict(exclude_unset=True)
 | 
			
		||||
    for key, value in product_data_dict.items():
 | 
			
		||||
        setattr(product, key, value)
 | 
			
		||||
    
 | 
			
		||||
    session.add(product)
 | 
			
		||||
    session.commit()
 | 
			
		||||
    session.refresh(product)
 | 
			
		||||
    return product
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@app.delete("/products/{product_id}", tags=["Products"])
 | 
			
		||||
def delete_product(product_id: int, session: Session = Depends(get_session)):
 | 
			
		||||
    """Delete a product"""
 | 
			
		||||
    product = session.get(Product, product_id)
 | 
			
		||||
    if not product:
 | 
			
		||||
        raise HTTPException(status_code=404, detail="Product not found")
 | 
			
		||||
    
 | 
			
		||||
    session.delete(product)
 | 
			
		||||
    session.commit()
 | 
			
		||||
    return {"message": "Product deleted successfully"}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Product Component endpoints
 | 
			
		||||
@app.post("/products/{product_id}/components/", response_model=ProductComponent, tags=["Product Components"])
 | 
			
		||||
def create_product_component(
 | 
			
		||||
    product_id: int, component: ProductComponent, session: Session = Depends(get_session)
 | 
			
		||||
):
 | 
			
		||||
    """Add a component to a product"""
 | 
			
		||||
    product = session.get(Product, product_id)
 | 
			
		||||
    if not product:
 | 
			
		||||
        raise HTTPException(status_code=404, detail="Product not found")
 | 
			
		||||
    
 | 
			
		||||
    component.product_id = product_id
 | 
			
		||||
    session.add(component)
 | 
			
		||||
    session.commit()
 | 
			
		||||
    session.refresh(component)
 | 
			
		||||
    return component
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@app.get("/products/{product_id}/components/", response_model=List[ProductComponent], tags=["Product Components"])
 | 
			
		||||
def read_product_components(
 | 
			
		||||
    product_id: int, session: Session = Depends(get_session)
 | 
			
		||||
):
 | 
			
		||||
    """Get all components for a product"""
 | 
			
		||||
    product = session.get(Product, product_id)
 | 
			
		||||
    if not product:
 | 
			
		||||
        raise HTTPException(status_code=404, detail="Product not found")
 | 
			
		||||
    
 | 
			
		||||
    return product.components
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Sale endpoints
 | 
			
		||||
@app.post("/sales/", response_model=Sale, tags=["Sales"])
 | 
			
		||||
def create_sale(sale: Sale, session: Session = Depends(get_session)):
 | 
			
		||||
    """Create a new sale"""
 | 
			
		||||
    # Ensure customer exists if customer_id is provided
 | 
			
		||||
    if sale.customer_id:
 | 
			
		||||
        customer = session.get(Customer, sale.customer_id)
 | 
			
		||||
        if not customer:
 | 
			
		||||
            raise HTTPException(status_code=404, detail="Customer not found")
 | 
			
		||||
    
 | 
			
		||||
    session.add(sale)
 | 
			
		||||
    session.commit()
 | 
			
		||||
    session.refresh(sale)
 | 
			
		||||
    return sale
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@app.get("/sales/", response_model=List[Sale], tags=["Sales"])
 | 
			
		||||
def read_sales(
 | 
			
		||||
    skip: int = 0, 
 | 
			
		||||
    limit: int = 100, 
 | 
			
		||||
    status: Optional[SaleStatus] = None,
 | 
			
		||||
    customer_id: Optional[int] = None,
 | 
			
		||||
    session: Session = Depends(get_session)
 | 
			
		||||
):
 | 
			
		||||
    """Get all sales with optional filtering"""
 | 
			
		||||
    query = select(Sale)
 | 
			
		||||
    
 | 
			
		||||
    if status:
 | 
			
		||||
        query = query.where(Sale.status == status)
 | 
			
		||||
    
 | 
			
		||||
    if customer_id:
 | 
			
		||||
        query = query.where(Sale.customer_id == customer_id)
 | 
			
		||||
    
 | 
			
		||||
    sales = session.exec(query.offset(skip).limit(limit)).all()
 | 
			
		||||
    return sales
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@app.get("/sales/{sale_id}", response_model=Sale, tags=["Sales"])
 | 
			
		||||
def read_sale(sale_id: int, session: Session = Depends(get_session)):
 | 
			
		||||
    """Get a sale by ID"""
 | 
			
		||||
    sale = session.get(Sale, sale_id)
 | 
			
		||||
    if not sale:
 | 
			
		||||
        raise HTTPException(status_code=404, detail="Sale not found")
 | 
			
		||||
    return sale
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@app.put("/sales/{sale_id}", response_model=Sale, tags=["Sales"])
 | 
			
		||||
def update_sale(
 | 
			
		||||
    sale_id: int, sale_data: Sale, session: Session = Depends(get_session)
 | 
			
		||||
):
 | 
			
		||||
    """Update a sale"""
 | 
			
		||||
    sale = session.get(Sale, sale_id)
 | 
			
		||||
    if not sale:
 | 
			
		||||
        raise HTTPException(status_code=404, detail="Sale not found")
 | 
			
		||||
    
 | 
			
		||||
    # Update sale attributes
 | 
			
		||||
    sale_data_dict = sale_data.dict(exclude_unset=True)
 | 
			
		||||
    for key, value in sale_data_dict.items():
 | 
			
		||||
        setattr(sale, key, value)
 | 
			
		||||
    
 | 
			
		||||
    session.add(sale)
 | 
			
		||||
    session.commit()
 | 
			
		||||
    session.refresh(sale)
 | 
			
		||||
    return sale
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@app.delete("/sales/{sale_id}", tags=["Sales"])
 | 
			
		||||
def delete_sale(sale_id: int, session: Session = Depends(get_session)):
 | 
			
		||||
    """Delete a sale"""
 | 
			
		||||
    sale = session.get(Sale, sale_id)
 | 
			
		||||
    if not sale:
 | 
			
		||||
        raise HTTPException(status_code=404, detail="Sale not found")
 | 
			
		||||
    
 | 
			
		||||
    session.delete(sale)
 | 
			
		||||
    session.commit()
 | 
			
		||||
    return {"message": "Sale deleted successfully"}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Sale Item endpoints
 | 
			
		||||
@app.post("/sales/{sale_id}/items/", response_model=SaleItem, tags=["Sale Items"])
 | 
			
		||||
def create_sale_item(
 | 
			
		||||
    sale_id: int, item: SaleItem, session: Session = Depends(get_session)
 | 
			
		||||
):
 | 
			
		||||
    """Add an item to a sale"""
 | 
			
		||||
    sale = session.get(Sale, sale_id)
 | 
			
		||||
    if not sale:
 | 
			
		||||
        raise HTTPException(status_code=404, detail="Sale not found")
 | 
			
		||||
    
 | 
			
		||||
    item.sale_id = sale_id
 | 
			
		||||
    session.add(item)
 | 
			
		||||
    session.commit()
 | 
			
		||||
    session.refresh(item)
 | 
			
		||||
    
 | 
			
		||||
    # Update the sale's total amount
 | 
			
		||||
    sale.add_item(item)
 | 
			
		||||
    session.add(sale)
 | 
			
		||||
    session.commit()
 | 
			
		||||
    
 | 
			
		||||
    return item
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@app.get("/sales/{sale_id}/items/", response_model=List[SaleItem], tags=["Sale Items"])
 | 
			
		||||
def read_sale_items(
 | 
			
		||||
    sale_id: int, session: Session = Depends(get_session)
 | 
			
		||||
):
 | 
			
		||||
    """Get all items for a sale"""
 | 
			
		||||
    sale = session.get(Sale, sale_id)
 | 
			
		||||
    if not sale:
 | 
			
		||||
        raise HTTPException(status_code=404, detail="Sale not found")
 | 
			
		||||
    
 | 
			
		||||
    return sale.items
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Convenience endpoints
 | 
			
		||||
@app.put("/sales/{sale_id}/status/{status}", response_model=Sale, tags=["Convenience"])
 | 
			
		||||
def update_sale_status(
 | 
			
		||||
    sale_id: int, status: SaleStatus, session: Session = Depends(get_session)
 | 
			
		||||
):
 | 
			
		||||
    """Update the status of a sale"""
 | 
			
		||||
    sale = session.get(Sale, sale_id)
 | 
			
		||||
    if not sale:
 | 
			
		||||
        raise HTTPException(status_code=404, detail="Sale not found")
 | 
			
		||||
    
 | 
			
		||||
    sale.update_status(status)
 | 
			
		||||
    session.add(sale)
 | 
			
		||||
    session.commit()
 | 
			
		||||
    session.refresh(sale)
 | 
			
		||||
    return sale
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@app.get("/products/available/", response_model=List[Product], tags=["Convenience"])
 | 
			
		||||
def get_available_products(
 | 
			
		||||
    istemplate: Optional[bool] = False,
 | 
			
		||||
    session: Session = Depends(get_session)
 | 
			
		||||
):
 | 
			
		||||
    """Get all available products"""
 | 
			
		||||
    query = select(Product).where(
 | 
			
		||||
        Product.status == ProductStatus.AVAILABLE,
 | 
			
		||||
        Product.purchase_till > datetime.utcnow(),
 | 
			
		||||
        Product.istemplate == istemplate
 | 
			
		||||
    )
 | 
			
		||||
    products = session.exec(query).all()
 | 
			
		||||
    return products
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@app.get("/customers/{customer_id}/sales/", response_model=List[Sale], tags=["Convenience"])
 | 
			
		||||
def get_customer_sales(
 | 
			
		||||
    customer_id: int, 
 | 
			
		||||
    status: Optional[SaleStatus] = None,
 | 
			
		||||
    session: Session = Depends(get_session)
 | 
			
		||||
):
 | 
			
		||||
    """Get all sales for a customer"""
 | 
			
		||||
    customer = session.get(Customer, customer_id)
 | 
			
		||||
    if not customer:
 | 
			
		||||
        raise HTTPException(status_code=404, detail="Customer not found")
 | 
			
		||||
    
 | 
			
		||||
    query = select(Sale).where(Sale.customer_id == customer_id)
 | 
			
		||||
    
 | 
			
		||||
    if status:
 | 
			
		||||
        query = query.where(Sale.status == status)
 | 
			
		||||
    
 | 
			
		||||
    sales = session.exec(query).all()
 | 
			
		||||
    return sales
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == "__main__":
 | 
			
		||||
    import uvicorn
 | 
			
		||||
    uvicorn.run(app, host="0.0.0.0", port=8000)
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								herodb/src/models/py/business.db
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								herodb/src/models/py/business.db
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										190
									
								
								herodb/src/models/py/example.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										190
									
								
								herodb/src/models/py/example.py
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,190 @@
 | 
			
		||||
#!/usr/bin/env python3
 | 
			
		||||
"""
 | 
			
		||||
Example script demonstrating the use of the business models.
 | 
			
		||||
"""
 | 
			
		||||
import datetime
 | 
			
		||||
from typing import List
 | 
			
		||||
 | 
			
		||||
from sqlmodel import Session, SQLModel, create_engine, select
 | 
			
		||||
 | 
			
		||||
from models import (
 | 
			
		||||
    Currency,
 | 
			
		||||
    Customer,
 | 
			
		||||
    Product,
 | 
			
		||||
    ProductComponent,
 | 
			
		||||
    ProductStatus,
 | 
			
		||||
    ProductType,
 | 
			
		||||
    Sale,
 | 
			
		||||
    SaleItem,
 | 
			
		||||
    SaleStatus,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def create_tables(engine):
 | 
			
		||||
    """Create all tables in the database"""
 | 
			
		||||
    SQLModel.metadata.create_all(engine)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def create_sample_data(session: Session) -> None:
 | 
			
		||||
    """Create sample data for demonstration"""
 | 
			
		||||
    # Create currencies
 | 
			
		||||
    usd = Currency(currency_code="USD", amount=0.0)
 | 
			
		||||
    eur = Currency(currency_code="EUR", amount=0.0)
 | 
			
		||||
    session.add(usd)
 | 
			
		||||
    session.add(eur)
 | 
			
		||||
    session.commit()
 | 
			
		||||
 | 
			
		||||
    # Create a customer
 | 
			
		||||
    customer = Customer.new(
 | 
			
		||||
        name="Acme Corporation",
 | 
			
		||||
        description="A fictional company",
 | 
			
		||||
        pubkey="acme123456",
 | 
			
		||||
        contact_sids=["circle1_contact123", "circle2_contact456"]
 | 
			
		||||
    )
 | 
			
		||||
    session.add(customer)
 | 
			
		||||
    session.commit()
 | 
			
		||||
 | 
			
		||||
    # Create product components
 | 
			
		||||
    cpu_component = ProductComponent.new(
 | 
			
		||||
        name="CPU",
 | 
			
		||||
        description="Central Processing Unit",
 | 
			
		||||
        quantity=1,
 | 
			
		||||
    )
 | 
			
		||||
    ram_component = ProductComponent.new(
 | 
			
		||||
        name="RAM",
 | 
			
		||||
        description="Random Access Memory",
 | 
			
		||||
        quantity=2,
 | 
			
		||||
    )
 | 
			
		||||
    session.add(cpu_component)
 | 
			
		||||
    session.add(ram_component)
 | 
			
		||||
    session.commit()
 | 
			
		||||
 | 
			
		||||
    # Create products
 | 
			
		||||
    laptop_price = Currency(currency_code="USD", amount=1200.0)
 | 
			
		||||
    session.add(laptop_price)
 | 
			
		||||
    session.commit()
 | 
			
		||||
 | 
			
		||||
    laptop = Product.new(
 | 
			
		||||
        name="Laptop",
 | 
			
		||||
        description="High-performance laptop",
 | 
			
		||||
        price=laptop_price,
 | 
			
		||||
        type_=ProductType.PRODUCT,
 | 
			
		||||
        category="Electronics",
 | 
			
		||||
        status=ProductStatus.AVAILABLE,
 | 
			
		||||
        max_amount=100,
 | 
			
		||||
        validity_days=365,
 | 
			
		||||
        istemplate=False,
 | 
			
		||||
    )
 | 
			
		||||
    laptop.add_component(cpu_component)
 | 
			
		||||
    laptop.add_component(ram_component)
 | 
			
		||||
    session.add(laptop)
 | 
			
		||||
    session.commit()
 | 
			
		||||
 | 
			
		||||
    support_price = Currency(currency_code="USD", amount=50.0)
 | 
			
		||||
    session.add(support_price)
 | 
			
		||||
    session.commit()
 | 
			
		||||
 | 
			
		||||
    support = Product.new(
 | 
			
		||||
        name="Technical Support",
 | 
			
		||||
        description="24/7 technical support",
 | 
			
		||||
        price=support_price,
 | 
			
		||||
        type_=ProductType.SERVICE,
 | 
			
		||||
        category="Support",
 | 
			
		||||
        status=ProductStatus.AVAILABLE,
 | 
			
		||||
        max_amount=1000,
 | 
			
		||||
        validity_days=30,
 | 
			
		||||
        istemplate=True,  # This is a template product
 | 
			
		||||
    )
 | 
			
		||||
    session.add(support)
 | 
			
		||||
    session.commit()
 | 
			
		||||
 | 
			
		||||
    # Create a sale
 | 
			
		||||
    sale = Sale.new(
 | 
			
		||||
        customer=customer,
 | 
			
		||||
        currency_code="USD",
 | 
			
		||||
    )
 | 
			
		||||
    session.add(sale)
 | 
			
		||||
    session.commit()
 | 
			
		||||
 | 
			
		||||
    # Create sale items
 | 
			
		||||
    laptop_unit_price = Currency(currency_code="USD", amount=1200.0)
 | 
			
		||||
    session.add(laptop_unit_price)
 | 
			
		||||
    session.commit()
 | 
			
		||||
 | 
			
		||||
    laptop_item = SaleItem.new(
 | 
			
		||||
        product=laptop,
 | 
			
		||||
        quantity=1,
 | 
			
		||||
        unit_price=laptop_unit_price,
 | 
			
		||||
        active_till=datetime.datetime.utcnow() + datetime.timedelta(days=365),
 | 
			
		||||
    )
 | 
			
		||||
    sale.add_item(laptop_item)
 | 
			
		||||
 | 
			
		||||
    support_unit_price = Currency(currency_code="USD", amount=50.0)
 | 
			
		||||
    session.add(support_unit_price)
 | 
			
		||||
    session.commit()
 | 
			
		||||
 | 
			
		||||
    support_item = SaleItem.new(
 | 
			
		||||
        product=support,
 | 
			
		||||
        quantity=2,
 | 
			
		||||
        unit_price=support_unit_price,
 | 
			
		||||
        active_till=datetime.datetime.utcnow() + datetime.timedelta(days=30),
 | 
			
		||||
    )
 | 
			
		||||
    sale.add_item(support_item)
 | 
			
		||||
 | 
			
		||||
    # Complete the sale
 | 
			
		||||
    sale.update_status(SaleStatus.COMPLETED)
 | 
			
		||||
    session.commit()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def query_data(session: Session) -> None:
 | 
			
		||||
    """Query and display data from the database"""
 | 
			
		||||
    print("\n=== Customers ===")
 | 
			
		||||
    customers = session.exec(select(Customer)).all()
 | 
			
		||||
    for customer in customers:
 | 
			
		||||
        print(f"Customer: {customer.name} ({customer.pubkey})")
 | 
			
		||||
        print(f"  Description: {customer.description}")
 | 
			
		||||
        print(f"  Contact SIDs: {', '.join(customer.contact_sids)}")
 | 
			
		||||
        print(f"  Created at: {customer.created_at}")
 | 
			
		||||
 | 
			
		||||
    print("\n=== Products ===")
 | 
			
		||||
    products = session.exec(select(Product)).all()
 | 
			
		||||
    for product in products:
 | 
			
		||||
        print(f"Product: {product.name} ({product.type_.value})")
 | 
			
		||||
        print(f"  Description: {product.description}")
 | 
			
		||||
        print(f"  Price: {product.price.amount} {product.price.currency_code}")
 | 
			
		||||
        print(f"  Status: {product.status.value}")
 | 
			
		||||
        print(f"  Is Template: {product.istemplate}")
 | 
			
		||||
        print(f"  Components:")
 | 
			
		||||
        for component in product.components:
 | 
			
		||||
            print(f"    - {component.name}: {component.quantity}")
 | 
			
		||||
 | 
			
		||||
    print("\n=== Sales ===")
 | 
			
		||||
    sales = session.exec(select(Sale)).all()
 | 
			
		||||
    for sale in sales:
 | 
			
		||||
        print(f"Sale to: {sale.customer.name}")
 | 
			
		||||
        print(f"  Status: {sale.status.value}")
 | 
			
		||||
        print(f"  Total: {sale.total_amount.amount} {sale.total_amount.currency_code}")
 | 
			
		||||
        print(f"  Items:")
 | 
			
		||||
        for item in sale.items:
 | 
			
		||||
            print(f"    - {item.name}: {item.quantity} x {item.unit_price.amount} = {item.subtotal.amount} {item.subtotal.currency_code}")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def main():
 | 
			
		||||
    """Main function"""
 | 
			
		||||
    print("Creating in-memory SQLite database...")
 | 
			
		||||
    engine = create_engine("sqlite:///business.db", echo=False)
 | 
			
		||||
    
 | 
			
		||||
    print("Creating tables...")
 | 
			
		||||
    create_tables(engine)
 | 
			
		||||
    
 | 
			
		||||
    print("Creating sample data...")
 | 
			
		||||
    with Session(engine) as session:
 | 
			
		||||
        create_sample_data(session)
 | 
			
		||||
    
 | 
			
		||||
    print("Querying data...")
 | 
			
		||||
    with Session(engine) as session:
 | 
			
		||||
        query_data(session)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == "__main__":
 | 
			
		||||
    main()
 | 
			
		||||
							
								
								
									
										49
									
								
								herodb/src/models/py/install_and_run.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										49
									
								
								herodb/src/models/py/install_and_run.sh
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,49 @@
 | 
			
		||||
#!/bin/bash
 | 
			
		||||
# Script to install dependencies using uv and run the example script
 | 
			
		||||
 | 
			
		||||
set -e  # Exit on error
 | 
			
		||||
 | 
			
		||||
# Change to the directory where this script is located
 | 
			
		||||
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
 | 
			
		||||
cd "$SCRIPT_DIR"
 | 
			
		||||
echo "Changed to directory: $SCRIPT_DIR"
 | 
			
		||||
 | 
			
		||||
# Define variables
 | 
			
		||||
VENV_DIR=".venv"
 | 
			
		||||
REQUIREMENTS="sqlmodel pydantic"
 | 
			
		||||
 | 
			
		||||
# Check if uv is installed
 | 
			
		||||
if ! command -v uv &> /dev/null; then
 | 
			
		||||
    echo "Error: uv is not installed."
 | 
			
		||||
    echo "Please install it using: curl -LsSf https://astral.sh/uv/install.sh | sh"
 | 
			
		||||
    exit 1
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
# Create virtual environment if it doesn't exist
 | 
			
		||||
if [ ! -d "$VENV_DIR" ]; then
 | 
			
		||||
    echo "Creating virtual environment..."
 | 
			
		||||
    uv venv "$VENV_DIR"
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
# Activate virtual environment
 | 
			
		||||
echo "Activating virtual environment..."
 | 
			
		||||
source "$VENV_DIR/bin/activate"
 | 
			
		||||
 | 
			
		||||
# Install dependencies
 | 
			
		||||
echo "Installing dependencies using uv..."
 | 
			
		||||
uv pip install $REQUIREMENTS
 | 
			
		||||
 | 
			
		||||
# Make example.py executable
 | 
			
		||||
chmod +x example.py
 | 
			
		||||
 | 
			
		||||
# Remove existing database file if it exists
 | 
			
		||||
if [ -f "business.db" ]; then
 | 
			
		||||
    echo "Removing existing database file..."
 | 
			
		||||
    rm business.db
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
# Run the example script
 | 
			
		||||
echo "Running example script..."
 | 
			
		||||
python example.py
 | 
			
		||||
 | 
			
		||||
echo "Done!"
 | 
			
		||||
							
								
								
									
										297
									
								
								herodb/src/models/py/models.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										297
									
								
								herodb/src/models/py/models.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,297 @@
 | 
			
		||||
"""
 | 
			
		||||
Python port of the business models from Rust using SQLModel.
 | 
			
		||||
"""
 | 
			
		||||
from datetime import datetime, timedelta
 | 
			
		||||
from enum import Enum
 | 
			
		||||
import json
 | 
			
		||||
from typing import List, Optional
 | 
			
		||||
 | 
			
		||||
from sqlmodel import Field, Relationship, SQLModel
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class SaleStatus(str, Enum):
 | 
			
		||||
    """SaleStatus represents the status of a sale"""
 | 
			
		||||
    PENDING = "pending"
 | 
			
		||||
    COMPLETED = "completed"
 | 
			
		||||
    CANCELLED = "cancelled"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ProductType(str, Enum):
 | 
			
		||||
    """ProductType represents the type of a product"""
 | 
			
		||||
    PRODUCT = "product"
 | 
			
		||||
    SERVICE = "service"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ProductStatus(str, Enum):
 | 
			
		||||
    """ProductStatus represents the status of a product"""
 | 
			
		||||
    AVAILABLE = "available"
 | 
			
		||||
    UNAVAILABLE = "unavailable"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Currency(SQLModel, table=True):
 | 
			
		||||
    """Currency represents a monetary value with amount and currency code"""
 | 
			
		||||
    id: Optional[int] = Field(default=None, primary_key=True)
 | 
			
		||||
    amount: float
 | 
			
		||||
    currency_code: str
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def new(cls, amount: float, currency_code: str) -> "Currency":
 | 
			
		||||
        """Create a new currency with amount and code"""
 | 
			
		||||
        return cls(amount=amount, currency_code=currency_code)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Customer(SQLModel, table=True):
 | 
			
		||||
    """Customer represents a customer who can purchase products or services"""
 | 
			
		||||
    id: Optional[int] = Field(default=None, primary_key=True)
 | 
			
		||||
    name: str
 | 
			
		||||
    description: str
 | 
			
		||||
    pubkey: str
 | 
			
		||||
    contact_sids_json: str = Field(default="[]")
 | 
			
		||||
    created_at: datetime = Field(default_factory=datetime.utcnow)
 | 
			
		||||
    updated_at: datetime = Field(default_factory=datetime.utcnow)
 | 
			
		||||
 | 
			
		||||
    # Relationships
 | 
			
		||||
    sales: List["Sale"] = Relationship(back_populates="customer")
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def contact_sids(self) -> List[str]:
 | 
			
		||||
        """Get the contact SIDs as a list"""
 | 
			
		||||
        return json.loads(self.contact_sids_json)
 | 
			
		||||
    
 | 
			
		||||
    @contact_sids.setter
 | 
			
		||||
    def contact_sids(self, value: List[str]) -> None:
 | 
			
		||||
        """Set the contact SIDs from a list"""
 | 
			
		||||
        self.contact_sids_json = json.dumps(value)
 | 
			
		||||
    
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def new(cls, name: str, description: str, pubkey: str, contact_sids: List[str] = None) -> "Customer":
 | 
			
		||||
        """Create a new customer with default timestamps"""
 | 
			
		||||
        customer = cls(
 | 
			
		||||
            name=name,
 | 
			
		||||
            description=description,
 | 
			
		||||
            pubkey=pubkey,
 | 
			
		||||
        )
 | 
			
		||||
        if contact_sids:
 | 
			
		||||
            customer.contact_sids = contact_sids
 | 
			
		||||
        return customer
 | 
			
		||||
 | 
			
		||||
    def add_contact(self, contact_id: int) -> None:
 | 
			
		||||
        """Add a contact ID to the customer"""
 | 
			
		||||
        # In a real implementation, this would add a relationship to a Contact model
 | 
			
		||||
        # For simplicity, we're not implementing the Contact model in this example
 | 
			
		||||
        self.updated_at = datetime.utcnow()
 | 
			
		||||
        
 | 
			
		||||
    def add_contact_sid(self, circle_id: str, object_id: str) -> None:
 | 
			
		||||
        """Add a smart ID (sid) to the customer's contact_sids list"""
 | 
			
		||||
        sid = f"{circle_id}_{object_id}"
 | 
			
		||||
        sids = self.contact_sids
 | 
			
		||||
        if sid not in sids:
 | 
			
		||||
            sids.append(sid)
 | 
			
		||||
            self.contact_sids = sids
 | 
			
		||||
            self.updated_at = datetime.utcnow()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ProductComponent(SQLModel, table=True):
 | 
			
		||||
    """ProductComponent represents a component of a product"""
 | 
			
		||||
    id: Optional[int] = Field(default=None, primary_key=True)
 | 
			
		||||
    name: str
 | 
			
		||||
    description: str
 | 
			
		||||
    quantity: int
 | 
			
		||||
    created_at: datetime = Field(default_factory=datetime.utcnow)
 | 
			
		||||
    updated_at: datetime = Field(default_factory=datetime.utcnow)
 | 
			
		||||
 | 
			
		||||
    # Relationships
 | 
			
		||||
    product_id: Optional[int] = Field(default=None, foreign_key="product.id")
 | 
			
		||||
    product: Optional["Product"] = Relationship(back_populates="components")
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def new(cls, name: str, description: str, quantity: int) -> "ProductComponent":
 | 
			
		||||
        """Create a new product component with default timestamps"""
 | 
			
		||||
        return cls(
 | 
			
		||||
            name=name,
 | 
			
		||||
            description=description,
 | 
			
		||||
            quantity=quantity,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Product(SQLModel, table=True):
 | 
			
		||||
    """Product represents a product or service offered"""
 | 
			
		||||
    id: Optional[int] = Field(default=None, primary_key=True)
 | 
			
		||||
    name: str
 | 
			
		||||
    description: str
 | 
			
		||||
    type_: ProductType = Field(sa_column_kwargs={"name": "type"})
 | 
			
		||||
    category: str
 | 
			
		||||
    status: ProductStatus
 | 
			
		||||
    max_amount: int
 | 
			
		||||
    purchase_till: datetime
 | 
			
		||||
    active_till: datetime
 | 
			
		||||
    istemplate: bool = Field(default=False)
 | 
			
		||||
    created_at: datetime = Field(default_factory=datetime.utcnow)
 | 
			
		||||
    updated_at: datetime = Field(default_factory=datetime.utcnow)
 | 
			
		||||
 | 
			
		||||
    # Price relationship
 | 
			
		||||
    price_id: Optional[int] = Field(default=None, foreign_key="currency.id")
 | 
			
		||||
    price: Optional[Currency] = Relationship()
 | 
			
		||||
 | 
			
		||||
    # Relationships
 | 
			
		||||
    components: List[ProductComponent] = Relationship(back_populates="product")
 | 
			
		||||
    sale_items: List["SaleItem"] = Relationship(back_populates="product")
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def new(
 | 
			
		||||
        cls,
 | 
			
		||||
        name: str,
 | 
			
		||||
        description: str,
 | 
			
		||||
        price: Currency,
 | 
			
		||||
        type_: ProductType,
 | 
			
		||||
        category: str,
 | 
			
		||||
        status: ProductStatus,
 | 
			
		||||
        max_amount: int,
 | 
			
		||||
        validity_days: int,
 | 
			
		||||
        istemplate: bool = False,
 | 
			
		||||
    ) -> "Product":
 | 
			
		||||
        """Create a new product with default timestamps"""
 | 
			
		||||
        now = datetime.utcnow()
 | 
			
		||||
        return cls(
 | 
			
		||||
            name=name,
 | 
			
		||||
            description=description,
 | 
			
		||||
            price=price,
 | 
			
		||||
            type_=type_,
 | 
			
		||||
            category=category,
 | 
			
		||||
            status=status,
 | 
			
		||||
            max_amount=max_amount,
 | 
			
		||||
            purchase_till=now + timedelta(days=365),
 | 
			
		||||
            active_till=now + timedelta(days=validity_days),
 | 
			
		||||
            istemplate=istemplate,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def add_component(self, component: ProductComponent) -> None:
 | 
			
		||||
        """Add a component to this product"""
 | 
			
		||||
        component.product = self
 | 
			
		||||
        self.components.append(component)
 | 
			
		||||
        self.updated_at = datetime.utcnow()
 | 
			
		||||
 | 
			
		||||
    def set_purchase_period(self, purchase_till: datetime) -> None:
 | 
			
		||||
        """Update the purchase availability timeframe"""
 | 
			
		||||
        self.purchase_till = purchase_till
 | 
			
		||||
        self.updated_at = datetime.utcnow()
 | 
			
		||||
 | 
			
		||||
    def set_active_period(self, active_till: datetime) -> None:
 | 
			
		||||
        """Update the active timeframe"""
 | 
			
		||||
        self.active_till = active_till
 | 
			
		||||
        self.updated_at = datetime.utcnow()
 | 
			
		||||
 | 
			
		||||
    def is_purchasable(self) -> bool:
 | 
			
		||||
        """Check if the product is available for purchase"""
 | 
			
		||||
        return self.status == ProductStatus.AVAILABLE and datetime.utcnow() <= self.purchase_till
 | 
			
		||||
 | 
			
		||||
    def is_active(self) -> bool:
 | 
			
		||||
        """Check if the product is still active (for services)"""
 | 
			
		||||
        return datetime.utcnow() <= self.active_till
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class SaleItem(SQLModel, table=True):
 | 
			
		||||
    """SaleItem represents an item in a sale"""
 | 
			
		||||
    id: Optional[int] = Field(default=None, primary_key=True)
 | 
			
		||||
    name: str
 | 
			
		||||
    quantity: int
 | 
			
		||||
    active_till: datetime
 | 
			
		||||
 | 
			
		||||
    # Relationships
 | 
			
		||||
    sale_id: Optional[int] = Field(default=None, foreign_key="sale.id")
 | 
			
		||||
    sale: Optional["Sale"] = Relationship(back_populates="items")
 | 
			
		||||
    
 | 
			
		||||
    product_id: Optional[int] = Field(default=None, foreign_key="product.id")
 | 
			
		||||
    product: Optional[Product] = Relationship(back_populates="sale_items")
 | 
			
		||||
    
 | 
			
		||||
    unit_price_id: Optional[int] = Field(default=None, foreign_key="currency.id")
 | 
			
		||||
    unit_price: Optional[Currency] = Relationship(sa_relationship_kwargs={"foreign_keys": "[SaleItem.unit_price_id]"})
 | 
			
		||||
    
 | 
			
		||||
    subtotal_id: Optional[int] = Field(default=None, foreign_key="currency.id")
 | 
			
		||||
    subtotal: Optional[Currency] = Relationship(sa_relationship_kwargs={"foreign_keys": "[SaleItem.subtotal_id]"})
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def new(
 | 
			
		||||
        cls,
 | 
			
		||||
        product: Product,
 | 
			
		||||
        quantity: int,
 | 
			
		||||
        unit_price: Currency,
 | 
			
		||||
        active_till: datetime,
 | 
			
		||||
    ) -> "SaleItem":
 | 
			
		||||
        """Create a new sale item"""
 | 
			
		||||
        # Calculate subtotal
 | 
			
		||||
        amount = unit_price.amount * quantity
 | 
			
		||||
        subtotal = Currency(
 | 
			
		||||
            amount=amount,
 | 
			
		||||
            currency_code=unit_price.currency_code,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        return cls(
 | 
			
		||||
            name=product.name,
 | 
			
		||||
            product=product,
 | 
			
		||||
            quantity=quantity,
 | 
			
		||||
            unit_price=unit_price,
 | 
			
		||||
            subtotal=subtotal,
 | 
			
		||||
            active_till=active_till,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Sale(SQLModel, table=True):
 | 
			
		||||
    """Sale represents a sale of products or services"""
 | 
			
		||||
    id: Optional[int] = Field(default=None, primary_key=True)
 | 
			
		||||
    status: SaleStatus
 | 
			
		||||
    sale_date: datetime = Field(default_factory=datetime.utcnow)
 | 
			
		||||
    created_at: datetime = Field(default_factory=datetime.utcnow)
 | 
			
		||||
    updated_at: datetime = Field(default_factory=datetime.utcnow)
 | 
			
		||||
 | 
			
		||||
    # Relationships
 | 
			
		||||
    customer_id: Optional[int] = Field(default=None, foreign_key="customer.id")
 | 
			
		||||
    customer: Optional[Customer] = Relationship(back_populates="sales")
 | 
			
		||||
    
 | 
			
		||||
    total_amount_id: Optional[int] = Field(default=None, foreign_key="currency.id")
 | 
			
		||||
    total_amount: Optional[Currency] = Relationship()
 | 
			
		||||
    
 | 
			
		||||
    items: List[SaleItem] = Relationship(back_populates="sale")
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def new(
 | 
			
		||||
        cls,
 | 
			
		||||
        customer: Customer,
 | 
			
		||||
        currency_code: str,
 | 
			
		||||
        status: SaleStatus = SaleStatus.PENDING,
 | 
			
		||||
    ) -> "Sale":
 | 
			
		||||
        """Create a new sale with default timestamps"""
 | 
			
		||||
        total_amount = Currency(amount=0.0, currency_code=currency_code)
 | 
			
		||||
        
 | 
			
		||||
        return cls(
 | 
			
		||||
            customer=customer,
 | 
			
		||||
            total_amount=total_amount,
 | 
			
		||||
            status=status,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def add_item(self, item: SaleItem) -> None:
 | 
			
		||||
        """Add an item to the sale and update the total amount"""
 | 
			
		||||
        item.sale = self
 | 
			
		||||
        
 | 
			
		||||
        # Update the total amount
 | 
			
		||||
        if not self.items:
 | 
			
		||||
            # First item, initialize the total amount with the same currency
 | 
			
		||||
            self.total_amount = Currency(
 | 
			
		||||
                amount=item.subtotal.amount,
 | 
			
		||||
                currency_code=item.subtotal.currency_code,
 | 
			
		||||
            )
 | 
			
		||||
        else:
 | 
			
		||||
            # Add to the existing total
 | 
			
		||||
            # (Assumes all items have the same currency)
 | 
			
		||||
            self.total_amount.amount += item.subtotal.amount
 | 
			
		||||
        
 | 
			
		||||
        # Add the item to the list
 | 
			
		||||
        self.items.append(item)
 | 
			
		||||
        
 | 
			
		||||
        # Update the sale timestamp
 | 
			
		||||
        self.updated_at = datetime.utcnow()
 | 
			
		||||
 | 
			
		||||
    def update_status(self, status: SaleStatus) -> None:
 | 
			
		||||
        """Update the status of the sale"""
 | 
			
		||||
        self.status = status
 | 
			
		||||
        self.updated_at = datetime.utcnow()
 | 
			
		||||
							
								
								
									
										42
									
								
								herodb/src/models/py/server.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										42
									
								
								herodb/src/models/py/server.sh
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,42 @@
 | 
			
		||||
#!/bin/bash
 | 
			
		||||
# Script to start the FastAPI server
 | 
			
		||||
 | 
			
		||||
set -e  # Exit on error
 | 
			
		||||
 | 
			
		||||
# Change to the directory where this script is located
 | 
			
		||||
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
 | 
			
		||||
cd "$SCRIPT_DIR"
 | 
			
		||||
echo "Changed to directory: $SCRIPT_DIR"
 | 
			
		||||
 | 
			
		||||
# Define variables
 | 
			
		||||
VENV_DIR=".venv"
 | 
			
		||||
REQUIREMENTS="sqlmodel pydantic fastapi uvicorn"
 | 
			
		||||
 | 
			
		||||
# Check if uv is installed
 | 
			
		||||
if ! command -v uv &> /dev/null; then
 | 
			
		||||
    echo "Error: uv is not installed."
 | 
			
		||||
    echo "Please install it using: curl -LsSf https://astral.sh/uv/install.sh | sh"
 | 
			
		||||
    exit 1
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
# Create virtual environment if it doesn't exist
 | 
			
		||||
if [ ! -d "$VENV_DIR" ]; then
 | 
			
		||||
    echo "Creating virtual environment..."
 | 
			
		||||
    uv venv "$VENV_DIR"
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
# Activate virtual environment
 | 
			
		||||
echo "Activating virtual environment..."
 | 
			
		||||
source "$VENV_DIR/bin/activate"
 | 
			
		||||
 | 
			
		||||
# Install dependencies
 | 
			
		||||
echo "Installing dependencies using uv..."
 | 
			
		||||
uv pip install $REQUIREMENTS
 | 
			
		||||
 | 
			
		||||
# Make api.py executable
 | 
			
		||||
chmod +x api.py
 | 
			
		||||
 | 
			
		||||
# Start the FastAPI server
 | 
			
		||||
echo "Starting FastAPI server..."
 | 
			
		||||
echo "API documentation available at: http://localhost:8000/docs"
 | 
			
		||||
uvicorn api:app --host 0.0.0.0 --port 8000 --reload
 | 
			
		||||
		Reference in New Issue
	
	Block a user