Skip to content

Validating models with contexts #130

Closed

Description

@NaqGuug

Currently if you want to validate or modify models according to some Context, you have to use either model_validate() or model_dump().

But let's say I have a User model (var user) I want to pass to a POST request. If I want to include contexts, I need to do user.model_dump(scim_ctx=Context.RESOURCE_CREATION_REQUEST). However, I want to pass the User model itself, not a dictionary, so I have to convert it back with User.model_validate(user.model_dump(scim_ctx=Context.RESOURCE_CREATION_REQUEST)). Now in the validation we can pass a second context like User.model_validate(user.model_dump(scim_ctx=Context.RESOURCE_CREATION_REQUEST) scim_ctx=Context.RESOURCE_CREATION_REQUEST).

I'm not sure if the last double context is necessary (edit: it is), but doing context checks becomes really annoying if I don't want to convert the model itself.

Suggestion
Some kind of helper methods for resources to just check the context stuff.

For example:

# Creates a new User according to context (in this case ignores read_only fields)
new_user: User = user.context(scim_ctx=Context.RESOURCE_CREATION_REQUEST)

# Validate if new user fits for POST (raise or return error if invalid, otherwise nothing)
new_user.context_validate(scim_ctx=Context.RESOURCE_CREATION_REQUEST)

Actually combining these into one function is even better

# Validate user model and return a new one
# (Error if missing required fields + ignore read_only)
 new_user: User = user.context_validate(scim_ctx=Context.RESOURCE_CREATION_REQUEST)

# OR same thing but modify the existing one (e.g. read_only fields to None)
user.context_validate(scim_ctx=Context.RESOURCE_CREATION_REQUEST)

Activity

  1. azmeuk commented on Feb 27, 2026

    @azmeuk
    Member

    Thank you for your suggestion.

    I am not sure I understand what you want to do in the first place. I'll try to reformulate, please correct me if I misunderstood.

    Your need is to prepare an object to be sent over the wire in a given context. What should happen if the object don't match the context you require? Would you expect the faulty attribute be reset or an exception be raised?

    What do you do with the object after? Do you use scim2-client? Because it more or less does this: the replace method for example takes an object, perform a model_dump with the right context, and reset the invalid fields or raise exceptions when it cannot be done.

  2. NaqGuug commented on Feb 27, 2026

    @NaqGuug
    ContributorAuthor

    So I will receive a scim object via FastAPI, validate that it's correct, transform that to other request/object and send it forward

    The main point is the validation in between. FastAPI will automatically convert the incoming request to desired scim model but without context checks. So I want to validate the already existing scim object with context and raise errors if there are problems.

    Same thing with response. I will receive a scim object directly so I won't run model_validate anywhere, which means I'm missing the context check.

    Example code

    @router.post(
        "/Groups",
        response_model=Group,
    )
    async def create_group(
        group_data: Group = Body(..., description="Group resource to create"),
    ) -> Group:
        """Create a new group."""
        # FastAPI already converts the request to the resource
        # Validate given group with proper context, raise errors if something is wrong
        group_data.context_validate(scim_ctx=Context.RESOURCE_CREATION_REQUEST)
    
        # Everything is fine with request, do something with it
        result: Group = await send_forward(group_data)
    
        # result is transformed directly to a Group object, so we don't run model_validate at any point
        # Check if the response resource is correct given the context and raise errors if not
        result.context_validate(scim_ctx=Context.RESOURCE_CREATION_RESPONSE)
        return result
  3. azmeuk commented on Apr 3, 2026

    @azmeuk
    Member

    In 0.6.8 I added context annotations that might help with FastAPI integration. Can you tell me how it helps you?

    I also documented FastAPI integration, I would love to hear your feedback about this.

  4. NaqGuug commented on Apr 7, 2026

    @NaqGuug
    ContributorAuthor

    Thanks, this is exactly what I was looking for! Defining the context directly in the type is magnificent. Too bad I can't use it as much because I have some dynamic models in the mix as well. But that's why we have the manual validation as well.

    The docs look great! There is a lot of stuff to handle at once, but with careful reading everything works out.
    However, I would change the SCIMResponse to be more FastAPI like. Take an example from JSONResponse, it explicitly defines the __init__ parameters and defines the return logic in render. You don't even have to define __init__ like seen in the documentation.

    class JSONResponse(Response):
        media_type = "application/json"
    
        def __init__(
            self,
            content: Any,
            status_code: int = 200,
            headers: Mapping[str, str] | None = None,
            media_type: str | None = None,
            background: BackgroundTask | None = None,
        ) -> None:
            super().__init__(content, status_code, headers, media_type, background)
    
        def render(self, content: Any) -> bytes:
            return json.dumps(
                content,
                ensure_ascii=False,
                allow_nan=False,
                indent=None,
                separators=(",", ":"),
            ).encode("utf-8")
  5. azmeuk commented on Apr 7, 2026

    @azmeuk
    Member

    I improved the SCIMResponse example and added context type aliases.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions