Skip to content

feat(profiles): rename ProfileDescriptor/DescriptorProfile to ProfileSpec/Profile (26.5.0)#44

Merged
discreteds merged 22 commits into
developfrom
feature/profile-spec-rename
May 13, 2026
Merged

feat(profiles): rename ProfileDescriptor/DescriptorProfile to ProfileSpec/Profile (26.5.0)#44
discreteds merged 22 commits into
developfrom
feature/profile-spec-rename

Conversation

@discreteds

Copy link
Copy Markdown
Member

Summary

Implements the 26.5.0 upstream half of docs/superpowers/specs/2026-05-13-profile-spec-rename-design.md.

  • ProfileDescriptorProfileSpec
  • DescriptorProfileProfile
  • __descriptor____spec__ (mirrored to the old name during 26.5.x for downstream readers)
  • descriptor_invariants_forspec_invariants_for
  • _MissingMissing (now public)
  • New public helper lookup_class_var()
  • Registry(spec_type=, profile_type=) constructor constraints
  • Argument-free @register (old @register(spec) form deprecated)

Old names remain available via PEP 562 module __getattr__ and class-attribute fallback with DeprecationWarning. All deprecation aliases are scheduled for removal in 26.6.0.

Test plan

  • hatch run test:test — 448 passing
  • hatch run ruff:check — clean
  • hatch build — wheel + sdist build cleanly as 26.5.0
  • python -W error::DeprecationWarning -c "import mountainash_settings" — no warnings on clean import
  • from mountainash_settings import ProfileDescriptor emits a DeprecationWarning pointing at ProfileSpec
  • from mountainash_settings import ProfileSpec, Profile, lookup_class_var, spec_invariants_for all resolve

Removal commitment

The deprecation aliases will be removed in mountainash-settings 26.6.0. Downstream consumers (mountainash-data, others) should follow the migration guide before that release.

Implementation history

21 commits implementing the 17-task plan at docs/superpowers/plans/2026-05-13-profile-spec-rename-upstream.md. Each commit verified with full test suite green; reviews caught and fixed bugs around __spec__ aliasing on __descriptor__-only classes, deprecation warning stacklevel, drift-catch error message, and decorator dispatch ordering.

🤖 Generated with Claude Code

discreteds and others added 22 commits May 13, 2026 17:48
17-task plan covering the mountainash-settings half of the profile/spec
rename design. Generated by superpowers:writing-plans from
docs/superpowers/specs/2026-05-13-profile-spec-rename-design.md.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New mountainash_settings.profiles.lookup module exposes lookup_class_var(cls, name),
which walks __mro__ and returns the first matching attribute. Used internally
by Profile for __spec__ and __adapter__ lookup; documented as supported public
API from 26.5.0 so downstream consumers can adopt it without taking a
private-dependency risk.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New canonical module mountainash_settings.profiles.spec exposes:
- ProfileSpec (was ProfileDescriptor)
- Missing (was _Missing, now public)
- MISSING (unchanged)
- ParameterSpec (unchanged)

Old names remain available from descriptor.py in this commit; the
descriptor.py shim conversion comes in a later task.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ribute

- Profile class replaces DescriptorProfile (old name aliased in Task 9).
- Class attribute __spec__ replaces __descriptor__.
- __pydantic_init_subclass__ accepts either attribute; raises TypeError
  on conflict, emits DeprecationWarning on __descriptor__-only.
- post_init() uses the public lookup_class_var helper for MRO walks.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Follow-up to commit 020be9c which missed this one rename.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
_resolve_spec() now sets cls.__spec__ = cls.__descriptor__ when falling
back to the deprecated attribute. Without this, classes that still
declare __descriptor__ would have fields installed correctly but every
call to profile_name/backend/provider_type/_default_kwargs() would
raise AttributeError on the missing __spec__.

Also bumps stacklevel from 3 to 4 so the DeprecationWarning points at
the user's 'class Foo(Profile):' line rather than pydantic's
ModelMetaclass.__new__ internals.

Test coverage extended to assert all four methods on a
__descriptor__-only class.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Registry(name, *, spec_type=ProfileSpec, profile_type=Profile) lets
domain-specific registries declare their constraints at construction time.
register() raises TypeError on spec or class type mismatch. Default values
preserve the existing wide-open behaviour for callers that don't pass
constraints.

Also mirrors __spec__ to __descriptor__ on register() during the 26.5.x
deprecation window (dropped in 26.6.0) so downstream code reading the old
attribute keeps working.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@register (no args) reads cls.__spec__ and registers — the new canonical
form, naming the spec exactly once. @register(spec) still works during
deprecation with a DeprecationWarning, and additionally raises TypeError
if the argument disagrees with cls.__spec__ (drift catch).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three review-driven fixes on top of 74737c6:

- Reorder isinstance branches: check ProfileSpec before type. The previous
  order also worked but the swapped form better expresses precedence and
  guards against metaclass-style spec subclasses.
- Drift-catch error now identifies specs by .name instead of repr(), which
  would otherwise produce a wall of ParameterSpec detail.
- Hoist the transitional ProfileDescriptor widening to module level as
  _SPEC_TYPES. Task 8 can now remove the widening in a single block.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Locks in the deprecation contract that downstream readers of
cls.__descriptor__ keep working through 26.5.x. Whole test class is
deleted in 26.6.0 when the mirror is dropped.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Function and generated test class renamed:
- descriptor_invariants_for() -> spec_invariants_for()
- TestDescriptorInvariants_<name> -> TestSpecInvariants_<name>
- parametrize ID 'descriptor' -> 'spec'

Old name aliased in Task 9.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
descriptor.py now re-exports MISSING and ParameterSpec from spec.py
(unchanged names) and uses PEP 562 __getattr__ to intercept the renamed
ProfileDescriptor and _Missing with DeprecationWarning.

BackendDescriptor is intentionally NOT aliased here — it's a
mountainash-data symbol owned downstream.

profiles/__init__.py updated to alias ProfileDescriptor = ProfileSpec
directly (no import from descriptor.py) to avoid spurious DeprecationWarning
at package import time.

registry.py updated to remove the try/except import of ProfileDescriptor
from descriptor.py — _SPEC_TYPES is now (ProfileSpec,) since ProfileDescriptor
IS ProfileSpec after this task.

test_descriptor.py removed; equivalent coverage in test_spec.py and
test_deprecation.py (next task).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
profiles/__init__.py exports ProfileSpec, Profile, Missing, lookup_class_var,
and spec_invariants_for as the canonical public names. The old names
ProfileDescriptor, DescriptorProfile, and descriptor_invariants_for resolve
via PEP 562 __getattr__ with DeprecationWarning until 26.6.0.

Top-level mountainash_settings/__init__.py updated to import and re-export
the new canonical names only (no deprecated aliases at package root).
test_public_api.py updated to use new names accordingly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
mountainash_settings.__init__ exports ProfileSpec, Profile, Missing,
lookup_class_var, and spec_invariants_for in __all__. Old names
(ProfileDescriptor, DescriptorProfile, descriptor_invariants_for) resolve
via PEP 562 __getattr__ with DeprecationWarning until 26.6.0.

test_public_api updated to use new names.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New tests/unit/profiles/test_deprecation.py asserts every deprecated entry
point still resolves, emits DeprecationWarning, and points at the right
new symbol. Also verifies the __spec__ -> __descriptor__ mirror on
classes registered via the new bare @register form.

Entire file is deleted in 26.6.0 along with the shims it covers.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Renames the why-when explanation doc and updates all type references
to the new vocabulary.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The Registry method is still named get_descriptor() in registry.py (kept
as backwards-compat name per the rename plan). Reverting the doc to match
the actual API.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Profile/ProfileSpec rename with deprecation aliases. See
docs/superpowers/specs/2026-05-13-profile-spec-rename-design.md for the
full design and migration guide.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ruff F822 flagged ProfileDescriptor and _Missing in __all__ as undefined
at module scope — they resolve via PEP 562 __getattr__, which ruff
doesn't know about. Removing them from __all__ is the correct fix and
also matches semantics: deprecated names should not be pulled in by
`from descriptor import *` silently.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@sonarqubecloud

Copy link
Copy Markdown

@discreteds discreteds merged commit d287259 into develop May 13, 2026
6 checks passed
@discreteds discreteds deleted the feature/profile-spec-rename branch May 13, 2026 09:06
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.

1 participant