Skip to content

Doc: clarify UseCaseService URL path naming convention + Pydantic v2 setattr restriction on model instances #109

Description

@allmonday

Summary

Two doc gaps that cost several hours of debugging each, encountered while building a service package with multiple UseCaseService subclasses. Filing as one doc issue since both are troubleshooting-adjacent and likely belong in the same getting-started / FAQ section.

Doc gap A: URL path naming convention for UseCaseService subclasses

Observation

A service class named ChecklistItemService is exposed at:

/api/checklist_item_service/<method_name>

Not (as one might expect from snake_case conversion of camelCase):

/api/check_list_item_service/<method_name>   ❌

The class name is lowercased as a single token; camelCase boundaries are not split on underscores.

Why this is confusing

  • Developers writing curl tests / SDK wrappers / reverse-proxy routes by hand will guess wrong paths and get 404.
  • It's not obvious from any doc page what rule is applied (the path appears correct in openapi.json after the fact, but you have to know to look there).
  • Different RPC frameworks use different conventions (FastAPI's own router uses snake_case-with-splits for class-based view names; gRPC uses fully-qualified names; etc.) — picking one explicitly and documenting it removes the guesswork.

Suggested doc location

references/lpk-builder.md (if it covers nexusx service URL layout) or a dedicated "Service URL Convention" section in the main README / getting-started. Something like:

URL convention. Each UseCaseService subclass is exposed at /api/<lowercase_class_name>/<method_name>. The class name is lowercased as a single token; camelCase boundaries are preserved (e.g., ChecklistItemService/api/checklist_item_service/, not /api/check_list_item_service/).

Doc gap B: Pydantic v2 forbids setattr of undeclared fields on SQLModel instances

Observation

When trying to attach runtime-computed data to a SQLModel instance (e.g., pre-fetched M:N associations, see related issue #108):

parent.targets = pre_loaded_list  # ❌ ValueError

Pydantic v2 raises:

ValueError: "Parent" object has no field "targets"

This is a Pydantic v2 + SQLModel interaction (not a nexusx bug per se), but it bites anyone following the natural "load related data, attach to model, model_validate" pattern that earlier Pydantic v1 / SQLAlchemy examples often used.

Suggested doc location

A troubleshooting / FAQ entry:

"X object has no field Y" when attaching runtime data to a model instance.

Pydantic v2 enforces strict schema on SQLModel instances: only fields declared on the model can be set via =. To pass runtime-computed data into a DTO, do the attachment on the DTO after model_validate, not on the model instance:

# ❌ Fails under Pydantic v2:
parent.targets = some_list
dto = ParentDTO.model_validate(parent)

# ✅ Works:
dto = ParentDTO.model_validate(parent)
dto.targets = [TargetDTO.model_validate(t) for t in some_list]

Alternatively, declare targets as a real relationship field on the model (then lazy="noload" + manual load + setattr works because Pydantic sees it as a declared field).

Why this is worth documenting

Related

Both of those issues benefit from clearer docs on the model → DTO lifecycle.

Metadata

Metadata

Assignees

No one assigned

    Labels

    documentationImprovements or additions to documentation

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions