Lenses — Aquilia Documentation
Comprehensive guide and documentation for Lenses in the Aquilia framework. View API reference, examples, and implementation patterns.
Contracts / Lenses Lenses Lenses are a special Facet type that renders related objects through another Contract. They provide depth-controlled, cycle-safe relational views — eliminating the need for manual nested serialization or N+1 query problems in your API responses. Core Concept A Lens is a Facet that delegates serialization to another Contract. When the parent Contract renders, each Lens field creates an instance of the target Contract and renders the related object through it. , , , ].map((item, i) => ( ))} Basic Usage from aquilia.contracts import Contract, TextFacet, IntFacet, Lens class CategoryContract(Contract): name = TextFacet(max_length=100) slug = TextFacet(max_length=100) class Spec: model = Category fields = ["id", "name", "slug"] class ReviewContract(Contract): rating = IntFacet(min_value=1, max_value=5) comment = TextFacet(max_length=500) author = TextFacet(source="author.username", read_only=True) class Spec: model = Review fields = ["id", "rating", "comment", "author"] class ProductContract(Contract): name = TextFacet(max_length=200) price = FloatFacet() # Single related object (ForeignKey) category = Lens(CategoryContract) # Multiple related objects (reverse FK / M2M) reviews = Lens(ReviewContract, many=True) class Spec: model = Product fields = "__all__" # Output: # , # "reviews": [ # , # , # ] # } Depth Control Lenses track nesting depth. When maximum depth is reached, the Lens falls back to rendering the primary key instead of the full nested object. # Default max_depth is 3 category = Lens(CategoryContract, max_depth=2) # At depth 0: Full nested object # At depth 1: Full nested object (still within limit) # At depth 2: Primary key only 5 (max_depth reached) # Custom depth for deep hierarchies comments = Lens(CommentContract, many=True, max_depth=5) # Example: Category with subcategories class CategoryContract(Contract): name = TextFacet() # Self-referential lens with depth control subcategories = Lens("CategoryContract", many=True, max_depth=3) parent = Lens("CategoryContract", max_depth=1) class Spec: model = Category fields = ["id", "name", "subcategories", "parent"] # Depth 0: ← PKs at max depth # ]} # ]} Cycle Detection If Contract A references Contract B which references Contract A, Aquilia detects this cycle and raises LensCycleFault instead of producing infinite recursion. class AuthorContract(Contract): name = TextFacet() books = Lens("BookContract", many=True) # Forward reference class Spec: model = Author fields = ["id", "name", "books"] class BookContract(Contract): title = TextFacet() author = Lens(AuthorContract) # Circular reference! class Spec: model = Book fields = ["id", "title", "author"] # Rendering: # AuthorContract renders → books → BookContract → author → AuthorContract # At this point, cycle is detected and rendering stops with PK fallback. # No LensCycleFault unless max_depth is also exceeded. # If you want to catch cycles explicitly: from aquilia.contracts.exceptions import LensCycleFault try: data = AuthorContract(instance=author).data except LensCycleFault as e: print(e.contract_chain) # ["AuthorContract", "BookContract", "AuthorContract"] Projection Selection with Subscript Syntax Use Python's subscript syntax Contract["projection"] to control which projection the nested Contract uses: class OrderContract(Contract): # Products rendered with minimal fields (fast list) items = Lens(ProductContract["__minimal__"], many=True) # Customer rendered with public-safe fields customer = Lens(CustomerContract["public"]) # Shipping address with full detail address = Lens(AddressContract["detail"]) class Spec: model = Order fields = "__all__" # Output: # , ← minimal # , ← minimal # ], # "customer": , ← public # "address": ← detail # } Forward References (Lazy Resolution) When Contract classes reference each other, use string names for forward references. They resolve lazily from the global Contract registry: class AuthorContract(Contract): name = TextFacet() # Forward reference — BookContract hasn't been defined yet books = Lens("BookContract", many=True) class Spec: model = Author fields = ["id", "name", "books"] # BookContract defined later class BookContract(Contract): title = TextFacet() author = Lens(AuthorContract) # Direct reference (already defined) class Spec: model = Book fields = ["id", "title", "author"] # Both work: # Lens(AuthorContract) — Direct class reference # Lens("AuthorContract") — String forward reference # Lens("BookContract") — Resolved from _contract_registry Lens Behavior on Input On inbound (input) data, Lenses accept the primary key value rather than nested objects. This prevents clients from arbitrarily modifying related objects through the parent: # Input payload for creating an order: } # If you want nested write support, use NestedContractFacet # from aquilia.contracts.annotations instead of Lens )
Go to Homepage