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 git+https://github.com/Alge/pydantic-visible-fields.git

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.2.tar.gz (21.9 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.2-py3-none-any.whl (11.9 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: pydantic_visible_fields-0.2.2.tar.gz
  • Upload date:
  • Size: 21.9 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.2.tar.gz
Algorithm Hash digest
SHA256 cc4bb59ae646c0dafc7f6ab6fc3a1f0e8aefd63131381ceec50e67a28246a3c2
MD5 6983ea30d41fea5b0b5cd125cf847154
BLAKE2b-256 22496f8891144aa1f3fad44af43c80b613bfb2a31900c32d84e4152dd1a335f2

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for pydantic_visible_fields-0.2.2-py3-none-any.whl
Algorithm Hash digest
SHA256 0072d0b445a7dba8413d27828e0ffb4825f41727caa7da8062811944f40f3f28
MD5 8a85fa96f5e163dcbc91b8ac06990650
BLAKE2b-256 46cbe144d69ba00516b83ca3a0042ba019681162002eb8809eb9b9a6536ea024

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