Skip to main content

Field-level visibility control for Pydantic models

Project description

Pydantic Visible Fields

A flexible field-level visibility control system for Pydantic models. This library allows you to define which fields of your models are visible to different user roles, making it easy to implement role-based access control at the data model level.

Python Pydantic License

Overview

pydantic-visible-fields provides a simple way to control which fields of your Pydantic models are visible to different user roles. It is particularly useful for API responses where you want to return different data depending on the user's role.

It also provides a PaginatedResponse class which makes it easy to generate paginated user responses with automatic conversion of objects to the correct visibility level.

Key Features

  • 🔒 Field-level visibility control using a simple decorator
  • 🧩 Class-level visibility control for more complex scenarios
  • 👥 Role inheritance to simplify permission management
  • 🔄 Nested model support with full recursive visibility control
  • 🌲 Circular reference handling to safely process complex object graphs
  • 🚀 Simple integration with FastAPI and other web frameworks

Installation

pip install pydantic-visible-fields

Basic Usage

1. Define Your Roles

First, define your roles as an enum:

from enum import Enum

class Role(str, Enum):
    VIEWER = "viewer"
    EDITOR = "editor"
    ADMIN = "admin"

2. Configure Role System

Configure the role inheritance hierarchy:

from pydantic_visible_fields import configure_roles

configure_roles(
    role_enum=Role,
    inheritance={
        Role.ADMIN: [Role.EDITOR],
        Role.EDITOR: [Role.VIEWER],
    },
    default_role=Role.VIEWER
)

3. Create Models with Visibility Rules

Use the VisibleFieldsModel base class and field decorator to control field visibility:

from pydantic_visible_fields import VisibleFieldsModel, field

class User(VisibleFieldsModel):
    id: str = field(visible_to=[Role.VIEWER, Role.EDITOR, Role.ADMIN])
    username: str = field(visible_to=[Role.VIEWER, Role.EDITOR, Role.ADMIN])
    email: str = field(visible_to=[Role.EDITOR, Role.ADMIN])
    hashed_password: str = field(visible_to=[Role.ADMIN])
    is_active: bool = field(visible_to=[Role.ADMIN])

4. Use in API Responses

In your API handlers, convert models to role-specific responses:

from fastapi import Depends
from pydantic_visible_fields import visible_fields_response

@app.get("/users/{user_id}")
async def get_user(user_id: str, current_user = Depends(get_current_user)):
    user = await get_user_by_id(user_id)

    # Return different fields based on user's role
    role = get_user_role(current_user)
    return visible_fields_response(user, role=role)

Advanced Usage

Class-Level Visibility

For more complex scenarios, you can define visibility at the class level:

from pydantic import BaseModel
from pydantic_visible_fields import VisibleFieldsMixin
from typing import ClassVar, Dict, Set

class UserSettings(BaseModel, VisibleFieldsMixin):
    _role_visible_fields: ClassVar[Dict[str, Set[str]]] = {
        Role.VIEWER: {"id", "theme"},
        Role.EDITOR: {"notifications"},
        Role.ADMIN: {"advanced_options", "debug_mode"},
    }

    id: str
    theme: str
    notifications: bool
    advanced_options: dict
    debug_mode: bool

Nested Models

Visibility control works recursively with nested models:

class Address(VisibleFieldsModel):
    street: str = field(visible_to=[Role.EDITOR, Role.ADMIN])
    city: str = field(visible_to=[Role.VIEWER, Role.EDITOR, Role.ADMIN])
    country: str = field(visible_to=[Role.VIEWER, Role.EDITOR, Role.ADMIN])
    postal_code: str = field(visible_to=[Role.EDITOR, Role.ADMIN])

class FullUser(VisibleFieldsModel):
    id: str = field(visible_to=[Role.VIEWER, Role.EDITOR, Role.ADMIN])
    username: str = field(visible_to=[Role.VIEWER, Role.EDITOR, Role.ADMIN])
    email: str = field(visible_to=[Role.EDITOR, Role.ADMIN])
    address: Address = field(visible_to=[Role.VIEWER, Role.EDITOR, Role.ADMIN])

Working with Collections

Visibility control works with lists and dictionaries of models:

class Team(VisibleFieldsModel):
    id: str = field(visible_to=[Role.VIEWER, Role.EDITOR, Role.ADMIN])
    name: str = field(visible_to=[Role.VIEWER, Role.EDITOR, Role.ADMIN])
    members: List[User] = field(visible_to=[Role.VIEWER, Role.EDITOR, Role.ADMIN])
    settings: Dict[str, Setting] = field(visible_to=[Role.EDITOR, Role.ADMIN])

Dynamic Visibility Configuration

You can dynamically configure visibility rules:

# Add a field to the VIEWER role
User.configure_visibility(Role.VIEWER.value, {"id", "username", "email"})

# Change all visible fields for ADMIN
User.configure_visibility(Role.ADMIN.value, {"id", "username", "email", "hashed_password", "is_active", "last_login"})

FastAPI Integration Example

Here's a complete example of how to use the library with FastAPI:

from enum import Enum
from fastapi import FastAPI, Depends, HTTPException
from typing import List

from pydantic_visible_fields import (
    VisibleFieldsModel, visible_fields_response, field, configure_roles
)

# Define roles
class Role(str, Enum):
    PUBLIC = "public"
    USER = "user"
    ADMIN = "admin"

# Configure role system
configure_roles(
    role_enum=Role,
    inheritance={
        Role.ADMIN: [Role.USER],
        Role.USER: [Role.PUBLIC],
    },
    default_role=Role.PUBLIC
)

# Models with visibility rules
class Item(VisibleFieldsModel):
    id: int = field(visible_to=[Role.PUBLIC, Role.USER, Role.ADMIN])
    name: str = field(visible_to=[Role.PUBLIC, Role.USER, Role.ADMIN])
    description: str = field(visible_to=[Role.USER, Role.ADMIN])
    price: float = field(visible_to=[Role.USER, Role.ADMIN])
    cost: float = field(visible_to=[Role.ADMIN])
    supplier: str = field(visible_to=[Role.ADMIN])

# Sample database
items_db = [
    Item(id=1, name="Item 1", description="Description 1", price=10.99, cost=5.50, supplier="Supplier A"),
    Item(id=2, name="Item 2", description="Description 2", price=20.99, cost=12.75, supplier="Supplier B"),
]

# FastAPI app
app = FastAPI()

# Dependency to get user role (in a real app, this would use auth logic)
def get_user_role(role_name: str = "public"):
    if role_name == "admin":
        return Role.ADMIN
    elif role_name == "user":
        return Role.USER
    return Role.PUBLIC

@app.get("/items/", response_model=List)
async def get_items(role = Depends(get_user_role)):
    # Convert items to role-specific responses
    return [visible_fields_response(item, role=role) for item in items_db]

@app.get("/items/{item_id}")
async def get_item(item_id: int, role = Depends(get_user_role)):
    for item in items_db:
        if item.id == item_id:
            # Return different fields based on user's role
            return visible_fields_response(item, role=role)
    raise HTTPException(status_code=404, detail="Item not found")

API Reference

Core Classes

  • VisibleFieldsModel - Base model class with field-level visibility control
  • VisibleFieldsMixin - Mixin class that can be added to any Pydantic model
  • PaginatedResponse - Class for handling pagination, automatically converts models to the correct visibility level

Functions

  • field(visible_to=None, **kwargs) - Field decorator to specify visibility
  • configure_roles(role_enum, inheritance=None, default_role=None) - Configure the role system
  • visible_fields_response(model, role=None) - Create a response with only visible fields

Methods

  • model.visible_dict(role=None) - Convert a model to a dictionary with only visible fields
  • model.to_response_model(role=None) - Convert a model to a response model class
  • Model.configure_visibility(role, visible_fields) - Configure visibility rules for a role

Development

Increasing version numbers

Use bump2version to increase the version number (included in the dev dependencies).

License

This project is licensed under the MIT License - see the LICENSE file for details.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pydantic_visible_fields-0.2.4.tar.gz (22.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pydantic_visible_fields-0.2.4-py3-none-any.whl (12.1 kB view details)

Uploaded Python 3

File details

Details for the file pydantic_visible_fields-0.2.4.tar.gz.

File metadata

  • Download URL: pydantic_visible_fields-0.2.4.tar.gz
  • Upload date:
  • Size: 22.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.11.2

File hashes

Hashes for pydantic_visible_fields-0.2.4.tar.gz
Algorithm Hash digest
SHA256 df900ee3e24133d4a22af2598744753176950a1f13b04b03a5fb4dd027d356cf
MD5 e08a17d807bd0b7fdf3e1783446f5714
BLAKE2b-256 aad2f1d695ccfe41e83696595458484c3e5c7770d93c011c8082eabfddfa0911

See more details on using hashes here.

File details

Details for the file pydantic_visible_fields-0.2.4-py3-none-any.whl.

File metadata

File hashes

Hashes for pydantic_visible_fields-0.2.4-py3-none-any.whl
Algorithm Hash digest
SHA256 0bb9a6ce9b51a159ed61b03cdc5127f3b96f32341ad4b767f8ce5527e3f0b76f
MD5 5d21c15c9eb95edcac7232465a12dabc
BLAKE2b-256 e461413fc5ca0f9642fbbf76c4e66325ac5d91d0b649010f377a385248829cd4

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page