Skip to content

refactor(models): migrate service layer and API models to Pydantic v2#82

Merged
alexsander-souza merged 24 commits intocanonical:resolute_supportfrom
alexsander-souza:pr/07-pydantic-v2-service-layer-models
Apr 7, 2026
Merged

refactor(models): migrate service layer and API models to Pydantic v2#82
alexsander-souza merged 24 commits intocanonical:resolute_supportfrom
alexsander-souza:pr/07-pydantic-v2-service-layer-models

Conversation

@alexsander-souza
Copy link
Copy Markdown
Contributor

@alexsander-souza alexsander-souza commented Mar 31, 2026

Replace all Pydantic v1 APIs with their v2 equivalents across
maasservicelayer, maasapiserver, and maastemporalworker. Key changes
include replacing GenericModel with BaseModel+Generic, validator with
field_validator/model_validator, dict()/parse_obj() with
model_dump()/model_validate(), and Config inner class with ConfigDict.
Modernise the builder code generator (generate_builders.py) to use
Pydantic v2 model_fields/FieldInfo API and AST-based parsing for import
preservation.

@alexsander-souza alexsander-souza force-pushed the pr/07-pydantic-v2-service-layer-models branch from c90f82d to 2fc68bf Compare April 1, 2026 15:58
@alexsander-souza alexsander-souza changed the title refactor(models): migrate service layer models to Pydantic v2 refactor: migrate to Pydantic v2 across service layer, builders, and API server Apr 1, 2026
@alexsander-souza alexsander-souza changed the title refactor: migrate to Pydantic v2 across service layer, builders, and API server refactor(models): migrate service layer and API models to Pydantic v2 Apr 1, 2026
@alexsander-souza alexsander-souza marked this pull request as ready for review April 1, 2026 19:20
Copy link
Copy Markdown
Contributor

@alemar99 alemar99 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some comments inline

Comment thread src/maasapiserver/v3/api/public/handlers/boot_resources.py Outdated
Comment thread src/maasapiserver/v3/api/public/handlers/boot_resources.py Outdated
Comment thread src/maasapiserver/v3/api/public/handlers/boot_source_selections.py Outdated
Comment thread src/tests/maasservicelayer/models/test_configurations.py Outdated
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR migrates MAAS service-layer, API-server, and temporal worker Pydantic models from Pydantic v1 APIs to Pydantic v2 equivalents, updating validation/serialization patterns and adjusting FastAPI request/response model behavior accordingly.

Changes:

  • Replaced Pydantic v1 APIs (parse_obj, dict, copy, validator/root_validator, GenericModel, Config) with Pydantic v2 APIs (model_validate, model_dump, model_copy, field_validator/model_validator, BaseModel+Generic, ConfigDict).
  • Updated custom field types (e.g. MAC/network types) to use Pydantic v2 core schema hooks and Annotated validators.
  • Updated tests and request handlers/models to reflect new serialization/validation behavior and schema defaults.

Reviewed changes

Copilot reviewed 189 out of 189 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
src/tests/maasservicelayer/vault/api/test_apiclient.py Update tests to model_validate.
src/tests/maasservicelayer/simplestreams/test_models.py Replace .dict() with model_dump().
src/tests/maasservicelayer/services/test_vlans.py Replace .copy() with model_copy().
src/tests/maasservicelayer/services/test_users.py Replace .copy() with model_copy().
src/tests/maasservicelayer/services/test_tags.py Replace .copy() with model_copy().
src/tests/maasservicelayer/services/test_subnets.py Replace .copy() with model_copy().
src/tests/maasservicelayer/services/test_notifications.py Replace .copy() with model_copy().
src/tests/maasservicelayer/services/test_image_sync.py Replace .copy() with model_copy().
src/tests/maasservicelayer/services/test_image_manifests.py Replace .copy() with model_copy().
src/tests/maasservicelayer/services/test_bootsourceselections.py Replace .copy() with model_copy().
src/tests/maasservicelayer/services/test_bootsourcecache.py Adjust bootloader fixture shape.
src/tests/maasservicelayer/services/test_bootresourcesets.py Add BootloaderFile fixture usage.
src/tests/maasservicelayer/services/test_bootresources.py Ensure versions are strings.
src/tests/maasservicelayer/services/test_base.py Remove v1-only required=False.
src/tests/maasservicelayer/models/test_configurations.py Adjust proxy URL normalization expectation.
src/tests/maasservicelayer/db/repositories/test_subnet_utilization.py Replace .dict() with model_dump().
src/tests/maasservicelayer/db/repositories/test_machines.py Replace .dict() with model_dump().
src/tests/maasservicelayer/db/repositories/test_interfaces.py Replace .dict() with model_dump().
src/tests/maasservicelayer/db/repositories/test_base.py Remove v1-only required=False.
src/tests/maasservicelayer/db/repositories/base.py Use model_dump() for comparisons.
src/tests/maasservicelayer/auth/test_external_oauth.py Replace .copy() with model_copy().
src/tests/maasservicelayer/auth/macaroons/test_macaroon_client.py Migrate to model_validate / v2 dumps.
src/tests/maasapiserver/v3/api/public/models/requests/test_base.py Rename order-by mapping attribute.
src/tests/maasapiserver/v3/api/public/handlers/test_vlans.py Replace .copy() with model_copy().
src/tests/maasapiserver/v3/api/public/handlers/test_users.py Replace .copy() with model_copy().
src/tests/maasapiserver/v3/api/public/handlers/test_tags.py Replace .copy() with model_copy().
src/tests/maasapiserver/v3/api/public/handlers/test_racks.py Replace .copy() with model_copy().
src/tests/maasapiserver/v3/api/public/handlers/test_package_repositories.py Replace .copy() with model_copy().
src/tests/maasapiserver/v3/api/public/handlers/test_ipranges.py Replace .copy() with model_copy().
src/tests/maasapiserver/v3/api/public/handlers/test_boot_sources.py Replace .copy() with model_copy().
src/tests/maasapiserver/v3/api/public/handlers/test_boot_source_selections.py Replace .copy() with model_copy().
src/maastemporalworker/converter.py Switch JSON encoding helper to to_jsonable_python.
src/maasservicelayer/vault/api/apiclient.py Use model_dump_json and model_validate.
src/maasservicelayer/services/machines_v2.py Replace Optional[...] with `
src/maasservicelayer/services/image_sync.py Replace .copy() with model_copy().
src/maasservicelayer/models/vlans.py Update optional typing to v2 style.
src/maasservicelayer/models/users.py Update optional typing to v2 style.
src/maasservicelayer/models/usergroups.py Update optional typing to v2 style.
src/maasservicelayer/models/ui_subnets.py Update optional typing to v2 style.
src/maasservicelayer/models/tokens.py Update optional typing to v2 style.
src/maasservicelayer/models/switches.py Update optional typing to v2 style.
src/maasservicelayer/models/subnets.py Update optional typing to v2 style.
src/maasservicelayer/models/staticipaddress.py Update optional typing to v2 style.
src/maasservicelayer/models/sshkeys.py Update optional typing to v2 style.
src/maasservicelayer/models/spaces.py Update optional typing to v2 style.
src/maasservicelayer/models/reservedips.py Update optional typing to v2 style.
src/maasservicelayer/models/notifications.py Update optional typing to v2 style.
src/maasservicelayer/models/nodes.py Update optional typing defaults.
src/maasservicelayer/models/neighbours.py Add defaults for optional fields.
src/maasservicelayer/models/machines.py Replace validators with field_validator.
src/maasservicelayer/models/ipranges.py Update optional typing to v2 style.
src/maasservicelayer/models/interfaces.py Update optional typing to v2 style.
src/maasservicelayer/models/filestorage.py Update optional typing to v2 style.
src/maasservicelayer/models/fields.py Rework network/MAC types for v2.
src/maasservicelayer/models/fabrics.py Update optional typing to v2 style.
src/maasservicelayer/models/events.py Update optional typing to v2 style.
src/maasservicelayer/models/domains.py Update optional typing to v2 style.
src/maasservicelayer/models/dnsresources.py Update optional typing to v2 style.
src/maasservicelayer/models/dnsresourcerecordsets.py Fix optional typing syntax.
src/maasservicelayer/models/dnsdata.py Update optional typing to v2 style.
src/maasservicelayer/models/dhcpsnippets.py Update optional typing to v2 style.
src/maasservicelayer/models/consumers.py Update optional typing to v2 style.
src/maasservicelayer/models/bootsources.py Update optional typing to v2 style.
src/maasservicelayer/models/base.py Switch to ConfigDict + v2 dumps.
src/maasservicelayer/models/auth.py Update optional typing + field defaults.
src/maasservicelayer/models/agents.py Update optional typing formatting.
src/maasservicelayer/db/init.py Switch JSON serializer to to_jsonable_python.
src/maasservicelayer/builders/zones.py Builder typing to v2 unions.
src/maasservicelayer/builders/vmcluster.py Builder typing to v2 unions.
src/maasservicelayer/builders/vlans.py Builder typing to v2 unions.
src/maasservicelayer/builders/users.py Builder typing to v2 unions.
src/maasservicelayer/builders/usergroups.py Builder typing to v2 unions.
src/maasservicelayer/builders/tokens.py Builder typing to v2 unions.
src/maasservicelayer/builders/tags.py Builder typing to v2 unions.
src/maasservicelayer/builders/switches.py Builder typing to v2 unions.
src/maasservicelayer/builders/subnets.py Builder CIDR typing changes.
src/maasservicelayer/builders/staticroutes.py Builder typing to v2 unions.
src/maasservicelayer/builders/staticipaddress.py Builder typing to v2 unions.
src/maasservicelayer/builders/sslkeys.py Builder typing to v2 unions.
src/maasservicelayer/builders/sshkeys.py Builder typing to v2 unions.
src/maasservicelayer/builders/spaces.py Builder typing to v2 unions.
src/maasservicelayer/builders/service_status.py Builder typing to v2 unions.
src/maasservicelayer/builders/scriptresult.py Builder typing to v2 unions.
src/maasservicelayer/builders/resource_pools.py Builder typing to v2 unions.
src/maasservicelayer/builders/reservedips.py Builder typing to v2 unions.
src/maasservicelayer/builders/rdns.py Builder typing to v2 unions.
src/maasservicelayer/builders/racks.py Builder typing to v2 unions.
src/maasservicelayer/builders/package_repositories.py Builder typing to v2 unions.
src/maasservicelayer/builders/openfga_tuple.py Builder typing to v2 unions.
src/maasservicelayer/builders/notifications.py Builder typing to v2 unions.
src/maasservicelayer/builders/nodes.py Builder typing to v2 unions.
src/maasservicelayer/builders/nodegrouptorackcontrollers.py Builder typing to v2 unions.
src/maasservicelayer/builders/neighbours.py Builder typing to v2 unions.
src/maasservicelayer/builders/mdns.py Builder typing to v2 unions.
src/maasservicelayer/builders/machines.py Builder typing to v2 unions.
src/maasservicelayer/builders/legacybootsourceselections.py Builder typing to v2 unions.
src/maasservicelayer/builders/leases.py Builder typing to v2 unions.
src/maasservicelayer/builders/ipranges.py Builder typing to v2 unions.
src/maasservicelayer/builders/interfaces.py Builder typing to v2 unions.
src/maasservicelayer/builders/image_manifests.py Builder generic union refactor.
src/maasservicelayer/builders/forwarddnsserver.py Builder typing to v2 unions.
src/maasservicelayer/builders/filestorage.py Builder typing to v2 unions.
src/maasservicelayer/builders/fabrics.py Builder typing to v2 unions.
src/maasservicelayer/builders/external_auth.py Builder typing to v2 unions.
src/maasservicelayer/builders/events.py Builder typing to v2 unions.
src/maasservicelayer/builders/domains.py Builder typing to v2 unions.
src/maasservicelayer/builders/dnsresources.py Builder typing to v2 unions.
src/maasservicelayer/builders/dnspublications.py Builder typing to v2 unions.
src/maasservicelayer/builders/dnsdata.py Builder typing to v2 unions.
src/maasservicelayer/builders/django_session.py Builder typing to v2 unions.
src/maasservicelayer/builders/dhcpsnippets.py Builder typing to v2 unions.
src/maasservicelayer/builders/consumers.py Builder typing to v2 unions.
src/maasservicelayer/builders/configurations.py Builder typing to v2 unions.
src/maasservicelayer/builders/bootstraptokens.py Builder typing to v2 unions.
src/maasservicelayer/builders/bootsourceselections.py Builder typing to v2 unions.
src/maasservicelayer/builders/bootsources.py Builder typing to v2 unions.
src/maasservicelayer/builders/bootsourcecache.py Builder typing to v2 unions.
src/maasservicelayer/builders/bootresourcesets.py Builder typing to v2 unions.
src/maasservicelayer/builders/bootresources.py Builder typing to v2 unions.
src/maasservicelayer/builders/bootresourcefilesync.py Builder typing to v2 unions.
src/maasservicelayer/builders/bootresourcefiles.py Builder typing to v2 unions.
src/maasservicelayer/builders/bmc.py Builder typing to v2 unions.
src/maasservicelayer/builders/agents.py Builder typing to v2 unions.
src/maasservicelayer/auth/macaroons/models/responses.py v2 validators + model validators.
src/maasservicelayer/auth/macaroons/models/requests.py v2 typing + aliases.
src/maasservicelayer/auth/macaroons/macaroon_client.py Switch to model_validate / v2 dumps.
src/maasapiserver/v3/middlewares/auth.py Use model_dump() for response composition.
src/maasapiserver/v3/api/public/models/responses/zones.py Make kind a Pydantic field.
src/maasapiserver/v3/api/public/models/responses/vlans.py Make kind a Pydantic field.
src/maasapiserver/v3/api/public/models/responses/users.py Make kind a Pydantic field.
src/maasapiserver/v3/api/public/models/responses/usergroups.py Make kind a Pydantic field.
src/maasapiserver/v3/api/public/models/responses/usergroup_members.py Make kind a Pydantic field.
src/maasapiserver/v3/api/public/models/responses/ui_subnets.py Make kind a Pydantic field.
src/maasapiserver/v3/api/public/models/responses/tags.py Make kind a Pydantic field.
src/maasapiserver/v3/api/public/models/responses/subnets.py Make kind a Pydantic field.
src/maasapiserver/v3/api/public/models/responses/staticroutes.py Make kind a Pydantic field.
src/maasapiserver/v3/api/public/models/responses/sslkey.py Make kind a Pydantic field.
src/maasapiserver/v3/api/public/models/responses/sshkeys.py Make kind a Pydantic field.
src/maasapiserver/v3/api/public/models/responses/spaces.py Make kind a Pydantic field.
src/maasapiserver/v3/api/public/models/responses/resource_pools.py Make kind a Pydantic field.
src/maasapiserver/v3/api/public/models/responses/reservedips.py Make kind a Pydantic field.
src/maasapiserver/v3/api/public/models/responses/racks.py Make kind a Pydantic field.
src/maasapiserver/v3/api/public/models/responses/package_repositories.py Make kind a Pydantic field.
src/maasapiserver/v3/api/public/models/responses/oauth2.py Make kind a Pydantic field.
src/maasapiserver/v3/api/public/models/responses/notifications.py Make kind a Pydantic field.
src/maasapiserver/v3/api/public/models/responses/machines.py Make kind a Pydantic field.
src/maasapiserver/v3/api/public/models/responses/ipranges.py Make kind a Pydantic field.
src/maasapiserver/v3/api/public/models/responses/interfaces.py Use ConfigDict; make kind a field.
src/maasapiserver/v3/api/public/models/responses/files.py Make kind a Pydantic field.
src/maasapiserver/v3/api/public/models/responses/fabrics.py Make kind a Pydantic field.
src/maasapiserver/v3/api/public/models/responses/events.py Make kind a Pydantic field.
src/maasapiserver/v3/api/public/models/responses/entitlements.py Make kind a Pydantic field.
src/maasapiserver/v3/api/public/models/responses/domains.py Make kind a Pydantic field.
src/maasapiserver/v3/api/public/models/responses/discoveries.py Make kind a Pydantic field.
src/maasapiserver/v3/api/public/models/responses/configurations.py Make kind a Pydantic field.
src/maasapiserver/v3/api/public/models/responses/boot_sources.py Make kind a Pydantic field.
src/maasapiserver/v3/api/public/models/responses/boot_source_selections.py Make kind a Pydantic field.
src/maasapiserver/v3/api/public/models/responses/boot_resources.py Make kind a Pydantic field.
src/maasapiserver/v3/api/public/models/responses/boot_images_common.py Make kind a Pydantic field.
src/maasapiserver/v3/api/public/models/responses/base.py Replace GenericModel with v2 generics + ConfigDict.
src/maasapiserver/v3/api/public/models/responses/agents.py Make kind a Pydantic field.
src/maasapiserver/v3/api/public/models/requests/zones.py Replace validator with field_validator.
src/maasapiserver/v3/api/public/models/requests/users.py Replace validator with field_validator.
src/maasapiserver/v3/api/public/models/requests/ui_subnets.py Rename order-by mapping attribute.
src/maasapiserver/v3/api/public/models/requests/tags.py Replace validator with field_validator.
src/maasapiserver/v3/api/public/models/requests/subnets.py Rewrite gateway validator for v2.
src/maasapiserver/v3/api/public/models/requests/staticroutes.py Replace conint with Annotated.
src/maasapiserver/v3/api/public/models/requests/sslkeys.py Replace validator with field_validator.
src/maasapiserver/v3/api/public/models/requests/package_repositories.py Replace root_validator with model_validator.
src/maasapiserver/v3/api/public/models/requests/notifications.py Replace root_validator with model_validator.
src/maasapiserver/v3/api/public/models/requests/ipranges.py Add model-level validation in v2 style.
src/maasapiserver/v3/api/public/models/requests/fabrics.py Replace validator with field_validator.
src/maasapiserver/v3/api/public/models/requests/external_auth.py Replace v1 validator signature with ValidationInfo.
src/maasapiserver/v3/api/public/models/requests/domains.py Replace root_validator with model_validator.
src/maasapiserver/v3/api/public/models/requests/discoveries.py Replace root_validator with model_validator.
src/maasapiserver/v3/api/public/models/requests/configurations.py Update type error message extraction.
src/maasapiserver/v3/api/public/models/requests/boot_sources.py Replace v1 validators; use ConfigDict(extra=\"forbid\").
src/maasapiserver/v3/api/public/models/requests/boot_source_selections.py Replace unique_items with custom validator.
src/maasapiserver/v3/api/public/models/requests/base.py Update order-by mapping, validators, query params.
src/maasapiserver/v3/api/public/models/dnsresourcerecordsets.py Replace validators with field_validator.
src/maasapiserver/v3/api/public/handlers/boot_source_selections.py Replace conlist with Annotated + Field constraints.
src/maasapiserver/v3/api/public/handlers/boot_resources.py Replace conlist; add explicit header dependency.
src/maasapiserver/v3/api/internal/models/responses/agent.py Make kind a Pydantic field; optional typing updates.
src/maasapiserver/v3/api/internal/models/requests/agent.py Replace validator with field_validator.
src/maasapiserver/main.py Wrap validation exception handler for new signature.
src/maasapiserver/common/api/models/responses/errors.py Make kind a Pydantic field.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/maasservicelayer/auth/macaroons/macaroon_client.py
Comment thread src/maasapiserver/v3/api/public/models/requests/base.py Outdated
Comment thread src/maasapiserver/v3/api/public/handlers/boot_source_selections.py
Comment thread src/maasapiserver/v3/api/public/handlers/boot_resources.py
Comment thread src/tests/maasservicelayer/auth/macaroons/test_macaroon_client.py Outdated
Comment thread src/tests/maasservicelayer/auth/macaroons/test_macaroon_client.py Outdated
Comment thread src/tests/maasservicelayer/auth/macaroons/test_macaroon_client.py
@alexsander-souza alexsander-souza force-pushed the pr/07-pydantic-v2-service-layer-models branch 2 times, most recently from db00a11 to f4a87b3 Compare April 2, 2026 16:45
Comment thread src/maasapiserver/v3/api/public/handlers/boot_resources.py Outdated
Comment thread src/maasapiserver/v3/api/public/models/requests/boot_source_selections.py Outdated
Comment thread src/maasservicelayer/auth/jwt.py Outdated
Comment thread src/maasservicelayer/auth/jwt.py Outdated
Comment thread src/maasservicelayer/vault/api/apiclient.py Outdated
Comment thread src/tests/maasservicelayer/services/test_leases.py
Comment thread src/maasservicelayer/vault/api/apiclient.py Outdated
Comment thread src/maasapiserver/v3/api/public/models/requests/subnets.py Outdated
Copy link
Copy Markdown
Contributor

@alemar99 alemar99 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1

alexsander-souza and others added 18 commits April 7, 2026 08:47
Replace all Pydantic v1 patterns with their v2 equivalents across the
35 service layer domain models, auth models, and simplestreams models.

- GenericModel → BaseModel (generics built-in to BaseModel in v2)
- class Config → model_config = ConfigDict(...)
- .dict() → .model_dump(); .parse_obj() → .model_validate()
- @validator → @field_validator with @classmethod
- @root_validator → @model_validator(mode="before"/"after")
- Optional[X] → X | None (PEP 604)
- configurations.py: Config(GenericModel, Generic[T]) →
  Config(BaseModel, Generic[T]) with ClassVar[ConfigDict]; underscore-
  prefixed class-level regex patterns annotated as ClassVar to prevent
  Pydantic v2 misidentifying them as PrivateAttr
- simplestreams/models.py: switch alias → validation_alias; remove
  dict() overrides; add populate_by_name=True; add squashfs = None
  default; add @model_validator(mode="after") to enforce
  squashfs-or-root_image_gz presence invariant
- fields.py: migrate custom type validators from __get_validators__ /
  __modify_schema__ to __get_pydantic_core_schema__ (v2 API)
Rewrite the builder code generator to emit Pydantic v2-compatible
annotations and regenerate all 56 builder files.

- generate_builders.py: replace ModelField with FieldDef dataclass;
  use model_fields instead of __fields__; use native union syntax
  (annotation | Unset) instead of mutating ModelField objects; emit
  Field(default=UNSET) without the invalid v2 required=False parameter
- 56 builder files: remove Field(required=False) which is not a valid
  Pydantic v2 Field parameter; verify reproducibility with:
    python utilities/generate_builders.py && git diff --exit-code
Migrate all maasapiserver v3 request and response models to Pydantic v2
and fix FastAPI integration points that changed with the v2 upgrade.

- responses/base.py: GenericModel -> BaseModel (generics built-in);
  class Config -> model_config = ConfigDict(populate_by_name=True)
- Response kind fields: bare class attribute -> str = Field(default=...)
  across all 33 public response files, 4 internal response models, and
  ErrorBodyResponse to restore JSON serialization (Pydantic v2 rejects
  non-annotated class attributes entirely)
- Request models: @validator -> @field_validator with @classmethod;
  @root_validator -> @model_validator(mode="after") with instance
  method signature; extra=Extra.forbid -> ConfigDict(extra="forbid");
  conlist -> Annotated[list[int], Field(min_length=1)]
- boot_source_selections.py: restore unique-items constraint via
  @model_validator(mode="after") checking duplicate tuples
- boot_resources.py handler: replace Header model injection with
  explicit get_boot_resource_create_request() dependency function
  (Pydantic v2 + FastAPI no longer support direct Header model
  injection via Depends())
- auth.py: .dict() -> .model_dump()
- db/__init__.py, converter.py: pydantic_encoder (removed in v2) ->
  pydantic_core.to_jsonable_python
Bring in the updated generate_builders.py from sandbox and regenerate
all 56 builder files to match.

Generator improvements:
- Use AST-based method/import extraction to avoid import-time failures
  when builders reference fields not yet present in the environment
- Emit X | Unset pipe-union syntax instead of Union[X, Unset]
- Add normalize_imports() to consolidate same-module imports
- Add outputs_match() using AST dump comparison for robust change
  detection (whitespace-insensitive)
- Add EXCLUDED_METHODS set covering magic methods, Pydantic internals,
  and Python 3.14 __annotate__/__annotate_func__
- Preserve file header (copyright comment) from existing builder files
- Handle import failures gracefully throughout BuilderCollection

Builder files: regenerated to use X | Unset syntax; drop the now-
redundant 'from typing import Union' import. subnets.py also corrected
to use IPv4v6Network instead of the stale IPv4Network | IPv6Network.

Verify reproducibility with:
    python utilities/generate_builders.py --check
Bring in updated models from sandbox.

- Use ABCMeta metaclass instead of ABC mixin for Pydantic compatibility
- Make Product and SimpleStreamsProductList generic (Generic[VersionT/ProductT])
  so get_latest_version/get_version_by_name return concrete types
- Switch from populate_by_name=True to validate_by_alias=True + serialize_by_alias=True
- Replace validation_alias with alias on hyphenated fields (consistent alias used
  for both input and output)
- Fix get_version_by_name: was comparing v.name, now correctly uses v.version_name
- Add at_least_one_file_present validator to BootloaderVersion
- Fix ImageFile.kpackage: add missing default=None
- Fix support_eol on ImageProduct: remove default so missing value is caught
- Guard preprocess_versions/preprocess_products against non-dict input
- Rename private _validate_* methods to validate_* (no leading underscore needed)
- Add type annotations to all validator signatures
- Replace Union[...] with | union syntax for SimpleStreamsProductListType
- Drop unused List, Type, Union imports; add Generic, TypeVar
…t layer

Complete the remaining Pydantic v1 → v2 migration items not covered by
the earlier model/builder/API commits.

- .json() → .model_dump_json(): macaroon_client, vault apiclient,
  test_macaroon_client
- .copy() → .model_copy(): image_sync service, external_oauth tests,
  bootsource* tests, image_* tests, notifications/subnets/tags/users/
  vlans service tests
- .dict() → .model_dump(): repository base tests, test_interfaces,
  test_machines, test_subnet_utilization, simplestreams test_models
- machines_v2: migrate 25 Optional[X] model fields to X | None = None
  (v2 no longer infers None default from Optional)
- test_bootresources: wrap randint version values in str() to satisfy
  strict field typing in Pydantic v2
- test_bootresourcesets/test_bootsourcecache: replace bare None bootloader
  file fields with populated BootloaderFile fixtures
…uality

- Add missing newlines at EOF for fields.py and base.py to prevent lint warnings
- Rename _order_by_columns to order_by_columns with explanation: leading underscores
  conflict with Pydantic v2 ClassVar handling; add comment for maintainers
- Move url_preserve_empty_path=True from base Config class to HttpProxyConfig only
  to avoid applying URL preservation logic to non-URL configuration types
- Add explanatory comments for FreeTextSearchQueryParam Header/Query field requirement
- Replace deprecated conint() with Annotated[int, Field(...)] in staticroutes.py
- Update all handler test files to use model_copy() instead of deprecated copy()
  for Pydantic v2 compatibility (boot_sources, vlans, ipranges, package_repositories,
  boot_source_selections, racks, tags, users tests)
- Add core_schema documentation to MacAddress and PackageRepoUrl explaining why
  __get_pydantic_core_schema__ is required for str subclasses with custom __new__
- Remove boolean flag duplication in BuilderModule._generate_output()
- Add debug logging to generate_builders.py for AST extraction and import failures
  to improve visibility when builders fall back to AST-only mode

Fixes: Missing newlines, overly broad config settings, inconsistent v1->v2 migrations
in test code, and improves debugging for builder generation edge cases.
- errors.py: HTTP_422_UNPROCESSABLE_ENTITY → HTTP_422_UNPROCESSABLE_CONTENT
- boot_resources/boot_source_selections: restore no-duplicate-ids constraint
  on bulk-delete query params (conlist unique_items equivalent via AfterValidator)
- boot_resources.py request model: Header → Field annotations; explicit
  default=None on optional fields
- boot_images_common.py: explicit default=None on optional response fields
- base.py: fix ClassVar OrderByQueryFilter default to avoid Pydantic v2 error
- app.py: wrap AsyncClient in ASGITransport (AsyncClient(app=...) deprecated
  in httpx 0.28)
- test_configurations.py: fix overly strict URL trailing-slash assertion
model_dump_json() returns a JSON string, not a dict; passing it as the
json= argument to aiohttp double-encodes the payload. model_dump(mode="json")
returns a JSON-serialisable dict as expected.
- Replace Pydantic v1 .json() with v2 .model_dump_json()
- Update subnet CIDR overlap test to use proper parameter compilation
- Fix test setup method naming convention (setup_method)
- Remove redundant setup() calls in async test methods
- Update JSON formatting in vault API client assertions

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Fix implicit string concatenation in test parametrization
- Add leeway to JWT claims validation for clock skew tolerance
- Allow blank subject claim in JWT validation

All tests now pass.
…derModule

The outputs_match() method was calling self.get_output() but BuilderModule
only had _generate_output(). Rename to get_output() for consistency with
BuilderModel and to match the expected API.
- Update AsyncClient fixture to use ASGITransport pattern in test_response_finalizer.py
- Fix ConfigurationResponse.value field to be optional (Any | None = None)
- Migrate test_auth.py from .dict() to .model_dump() for Pydantic v2
- Fix metadata comparison in test_get_active_oauth_provider_success using model_dump()
- Update validation error expectations in test_notifications.py for Pydantic v2 format
- Update validation error messages in test_sshkeys.py to match Pydantic v2 error format
… type

Add UniqueList[T] to maasservicelayer/models/fields.py using pydantic v2's
AfterValidator pattern. Raises a typed 'unique_list' PydanticCustomError on
duplicates and emits uniqueItems: true in the JSON/OpenAPI schema via
Field(json_schema_extra).

Replace _no_duplicate_ids + AfterValidator in bulk_delete_custom_images and
the manual model_validator loop in BulkSelectionRequest with UniqueList.
SelectionRequest gains frozen=True and an explicit __hash__ so it satisfies
the Hashable bound required by UniqueList.
- Fix subnets.py: Use is_link_local property instead of incorrect fe80::/64 check
- Fix subnet.py: Update comment to reference correct fe80::/10 mask
- Fix test_subnet.py: Update test data to use fe80::/10 instead of fe80::/64

The IPv6 link-local range is fe80::/10 (not /64). The is_link_local property
correctly checks the full range per RFC 4291.
- Store iat and exp claims as integer timestamps (second precision)
  joserfc validates NumericDate by comparing against int(time.time()),
  so float timestamps would always appear 'in the future'.

- Fix test assertion: token.issued can be up to ~1s behind now due
  to integer truncation, so use relative time check instead of >=.

- Replace invalid empty-subject test case with valid one. Empty 'sub'
  claim is correctly rejected by joserfc when essential: True.
- Change builder_model fixture to return type[OAuthProviderBuilder] (class type)
- Update test method type hints to properly indicate builder_model parameter as class type
- Instantiate builder with no arguments and only set required properties per test
- Fixes tests: test_create_conflict, test_update_provider_success, test_update_provider_enables_when_none_enabled, test_update_provider_conflict
- All 50 TestExternalOAuthService tests pass
Replace all Pydantic v1 patterns with their v2 equivalents across the
35 service layer domain models and auth models.

- GenericModel → BaseModel (generics built-in to BaseModel in v2)
- class Config → model_config = ConfigDict(...)
- .dict() → .model_dump(); .parse_obj() → .model_validate()
- @validator → @field_validator with @classmethod
- Optional[X] → X | None (PEP 604)
- configurations.py: Config(GenericModel, Generic[T]) →
  Config(BaseModel, Generic[T]) with ClassVar[ConfigDict]
- simplestreams/models.py: add squashfs = None default; add
  @model_validator(mode="after") to enforce squashfs-or-root_image_gz
  presence invariant (replaces implicit v1 field constraint behaviour)
@alexsander-souza alexsander-souza force-pushed the pr/07-pydantic-v2-service-layer-models branch from b6c6341 to f77ee17 Compare April 7, 2026 11:48
@alexsander-souza alexsander-souza merged commit 7d50283 into canonical:resolute_support Apr 7, 2026
1 of 2 checks passed
@alexsander-souza alexsander-souza deleted the pr/07-pydantic-v2-service-layer-models branch April 7, 2026 17:56
alexsander-souza added a commit that referenced this pull request Apr 8, 2026
…#82)

Replace all Pydantic v1 APIs with their v2 equivalents across maasservicelayer, maasapiserver, and maastemporalworker. Key changes include replacing GenericModel with BaseModel+Generic, validator with field_validator/model_validator, dict()/parse_obj() with model_dump()/model_validate(), and Config inner class with ConfigDict. Modernise the builder code generator (generate_builders.py) to use Pydantic v2 model_fields/FieldInfo API and AST-based parsing for import preservation.
alexsander-souza added a commit that referenced this pull request Apr 8, 2026
…#82)

Replace all Pydantic v1 APIs with their v2 equivalents across maasservicelayer, maasapiserver, and maastemporalworker. Key changes include replacing GenericModel with BaseModel+Generic, validator with field_validator/model_validator, dict()/parse_obj() with model_dump()/model_validate(), and Config inner class with ConfigDict. Modernise the builder code generator (generate_builders.py) to use Pydantic v2 model_fields/FieldInfo API and AST-based parsing for import preservation.
alexsander-souza added a commit that referenced this pull request Apr 9, 2026
…#82)

Replace all Pydantic v1 APIs with their v2 equivalents across maasservicelayer, maasapiserver, and maastemporalworker. Key changes include replacing GenericModel with BaseModel+Generic, validator with field_validator/model_validator, dict()/parse_obj() with model_dump()/model_validate(), and Config inner class with ConfigDict. Modernise the builder code generator (generate_builders.py) to use Pydantic v2 model_fields/FieldInfo API and AST-based parsing for import preservation.
alexsander-souza added a commit that referenced this pull request Apr 17, 2026
…#82)

Replace all Pydantic v1 APIs with their v2 equivalents across maasservicelayer, maasapiserver, and maastemporalworker. Key changes include replacing GenericModel with BaseModel+Generic, validator with field_validator/model_validator, dict()/parse_obj() with model_dump()/model_validate(), and Config inner class with ConfigDict. Modernise the builder code generator (generate_builders.py) to use Pydantic v2 model_fields/FieldInfo API and AST-based parsing for import preservation.
alexsander-souza added a commit that referenced this pull request Apr 17, 2026
…#82)

Replace all Pydantic v1 APIs with their v2 equivalents across maasservicelayer, maasapiserver, and maastemporalworker. Key changes include replacing GenericModel with BaseModel+Generic, validator with field_validator/model_validator, dict()/parse_obj() with model_dump()/model_validate(), and Config inner class with ConfigDict. Modernise the builder code generator (generate_builders.py) to use Pydantic v2 model_fields/FieldInfo API and AST-based parsing for import preservation.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants