Layouts
Some websites serve multiple layouts for the same type of page.
For example, a product page may have different layouts for different product categories, and web-sites sometimes undergo A-B tests where the same page can have multiple variations.
In these cases, a good approach is to define one page object per layout, and then define a main page object that picks the right layout for the current response.
To avoid writing field-forwarding boilerplate in the main page object, use the
layout_switch() decorator.
Basic usage
With layout_switch(), your main page object:
declares layout page objects as inputs, and
defines a switch method that returns the selected layout page object.
For example:
import attrs
from web_poet import HttpResponse, ItemPage, WebPage, field, layout_switch
@attrs.define
class Product:
title: str
price: str
class ProductLayoutA(WebPage[Product]):
@field
def title(self):
return self.css("h1::text").get()
@field
def price(self):
return self.css(".price::text").get()
class ProductLayoutB(WebPage[Product]):
@field
def title(self):
return self.css(".title::text").get()
@layout_switch()
@attrs.define
class ProductPage(ItemPage[Product]):
response: HttpResponse
layout_a: ProductLayoutA
layout_b: ProductLayoutB
def get_layout(self) -> ItemPage[Product]:
if self.response.css(".layout-a"):
return self.layout_a
return self.layout_b
@field
def price(self):
return "N/A"
Field forwarding rules
By default, layout_switch() forwards fields based on the output
item type field names.
This means that for item classes with declared fields (for example attrs, dataclasses, or pydantic models), the forwarded field set matches the item schema.
For each forwarded field:
if the selected layout defines the field, that layout field is used, and
if the selected layout does not define the field, a same-name field in the main page object is used as fallback.
For output item types that do not expose field names (for example dict),
pass layout classes explicitly:
@layout_switch(layouts=[ProductLayoutA, ProductLayoutB])
@attrs.define
class ProductPage(ItemPage[dict]): ...
When layouts is provided, layout_switch() forwards the
union of fields defined across those layout classes.