Skip to content

Service Class Refactoring - Align with Characteristic Architecture#94

Merged
RonanB96 merged 6 commits intomainfrom
copilot/fix-07a7a865-4acf-4700-8b09-8c2dc47d37dc
Oct 7, 2025
Merged

Service Class Refactoring - Align with Characteristic Architecture#94
RonanB96 merged 6 commits intomainfrom
copilot/fix-07a7a865-4acf-4700-8b09-8c2dc47d37dc

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Oct 6, 2025

  • Phase 1: Create foundation classes
    • Create SIGServiceResolver class with resolution logic
    • Create ServiceValidationConfig dataclass
    • Add is_sig_service() method to UUID class
  • Phase 2: Refactor BaseGattService
    • Remove @DataClass decorator from BaseGattService
    • Add __init__(info, validation) method
    • Add __post_init__() for SIG resolution
    • Replace uuid/name/summary properties to read from _info
    • Add get_class_uuid() class method
  • Phase 3: Refactor CustomBaseGattService
    • Add __init_subclass__ hook with allow_sig_override
    • Add class-level _info attribute support
    • Add _configured_info class attribute
    • Override __post_init__() for custom info management
    • Create UnknownService class
  • Phase 4: Remove @DataClass from concrete services
    • Remove @DataClass from all concrete service implementations
    • Update device.py UnknownService for new architecture
  • Phase 5: Run linting and tests
    • Run format check ✅
    • Run lint check ✅
    • Run all tests ✅ (805 passed)
  • Phase 6: Address code review feedback
    • Optimize service name resolution order by hit rate
    • Clean up unnecessary comments
    • Fix attribute checking pattern
    • Relax timing test threshold
Original prompt

This section details on the original issue you should resolve

<issue_title># Service Class Refactoring</issue_title>
<issue_description># Service Class Refactoring - Executive Summary

Created: 2025-10-06
Full Guide: docs/SERVICE_CLASS_REFACTORING_GUIDE.md
Status: Architecture Specification Ready


Quick Overview

This document summarizes the architectural refactoring needed to align BaseGattService and CustomBaseGattService with the proven patterns from BaseCharacteristic and CustomBaseCharacteristic.


Key Changes Required

1. Single Source of Truth Pattern

  • Create _info: ServiceInfo as the single source for all service metadata
  • All properties (uuid, name, summary) read from _info
  • No more inline UUID resolution in properties

2. Resolver Pattern

  • Create SIGServiceResolver class (parallel to SIGCharacteristicResolver)
  • Separate resolution logic from constructor
  • Support YAML and registry fallback paths

3. Constructor Refactoring

  • Remove @dataclass decorator from BaseGattService
  • Add __init__(info: ServiceInfo | None, validation: ServiceValidationConfig | None)
  • Add __post_init__() method for resolution

4. Custom Service Enhancement

  • Add __init_subclass__ hook to CustomBaseGattService
  • Support class-level _info attribute (Progressive API Level 2)
  • Add allow_sig_override parameter for intentional SIG UUID reuse
  • Create UnknownService class for dynamic discovery

5. Class-Level Resolution

  • Add get_class_uuid() class method for registry lookups
  • Support resolution without instantiation

Progressive API Levels

Level 1: Manual Info (Legacy)

class MyService(CustomBaseGattService):
    def __init__(self):
        super().__init__(info=ServiceInfo(...))

Level 2: Class-Level Info (Recommended)

class MyService(CustomBaseGattService):
    _info = ServiceInfo(
        uuid=BluetoothUUID("0000ABCD-..."),
        name="My Service",
    )

Level 3: Automatic SIG Resolution

class BatteryService(BaseGattService):
    pass  # Auto-resolved from SIG specs!

Implementation Phases

Phase 1: Foundation ✅

  • Create SIGServiceResolver
  • Add ServiceValidationConfig
  • Add ServiceSpec to YAML system
  • Add is_sig_service() to UUID class

Phase 2: Internal Refactoring ⏳

  • Refactor BaseGattService.__init__()
  • Refactor properties to use _info
  • Add manual override support

Phase 3: Custom Services ⏳

  • Add __init_subclass__ to CustomBaseGattService
  • Add SIG override validation
  • Create UnknownService class

Phase 4: Testing ⏳

  • Test all resolution paths
  • Test custom service patterns
  • Update existing services

Phase 5: Documentation ⏳

  • Update docstrings
  • Create migration examples
  • Update architecture docs

Critical Benefits

  1. Consistency: Services match characteristic patterns exactly
  2. Maintainability: Single place to understand resolution logic
  3. Testability: Resolver can be tested independently
  4. Safety: SIG UUID override requires explicit permission
  5. Flexibility: Multiple API levels for different use cases
  6. Discoverability: Class-level UUID resolution for registries

Key Patterns to Follow

DO: Use _info as single source of truth
DO: Put resolution logic in SIGServiceResolver
DO: Support class-level _info for custom services
DO: Validate SIG UUID overrides in __init_subclass__

DON'T: Put resolution logic in properties
DON'T: Use SIG UUIDs in custom services without permission
DON'T: Mix instance creation with class-level resolution
DON'T: Skip __post_init__ for info resolution


Testing Requirements

Must Test:

  • ✅ SIG service auto-resolution (all name formats)
  • ✅ Custom service with class-level _info
  • ✅ SIG UUID override blocking (without permission)
  • ✅ SIG UUID override allowing (with permission)
  • ✅ Class-level UUID resolution
  • ✅ Unknown service characteristic processing
  • ✅ Registry fallback paths
  • ✅ YAML resolution paths

Example Migration

Before:

@dataclass
class BatteryService(BaseGattService):
    _service_name: str = "Battery"
    
    @property
    def uuid(self) -> BluetoothUUID:
        info = uuid_registry.get_service_info(self._service_name)
        return info.uuid

After:

class BatteryService(BaseGattService):
    _service_name = "Battery"
    
    # UUID now resolved automatically in __post_init__
    # Property reads from _info
    @property
    def uuid(self) -> BluetoothUUID:
        return self._info.uuid

Next Steps

  1. Review full guide: docs/SERVICE_CLASS_REFACTORING_GUIDE.md
  2. Implement Phase 1 (foundation classes)
  3. Add comprehensive tests for new patterns
  4. Migrate existing services one by one
  5. Update documentation and examples

Questions & Clar...

Fixes #93

💬 Share your feedback on Copilot coding agent for the chance to win a $200 gift card! Click here to start the survey.

@RonanB96
Copy link
Copy Markdown
Owner

RonanB96 commented Oct 6, 2025

@copilot no legacy or backward compatibility code, just get the arch in line with how the chars are done. This means removing the dataclasses for the services, follow best practices. The overall code should follow a very similar if not the same arch to be maintainable and understandable

Copy link
Copy Markdown
Contributor Author

Copilot AI commented Oct 6, 2025

@copilot no legacy or backward compatibility code, just get the arch in line with how the chars are done. This means removing the dataclasses for the services, follow best practices. The overall code ...

Done! I've removed all @DataClass decorators from service classes and aligned the architecture exactly with how characteristics work:

Key Changes:

  • BaseGattService now has proper __init__() and __post_init__() methods (no @DataClass)
  • Added SIGServiceResolver class for clean resolution logic separation
  • Made _info: ServiceInfo the single source of truth for uuid/name/summary properties
  • CustomBaseGattService has __init_subclass__() hook with SIG UUID override validation
  • All concrete service classes (BatteryService, DeviceInformationService, etc.) no longer use @DataClass
  • Added UnknownService class following the same pattern as UnknownCharacteristic

All 805 tests pass, formatting and linting are clean. The service architecture now mirrors the characteristic architecture exactly.

- Remove @DataClass from BaseGattService and all concrete services
- Add SIGServiceResolver for consistent resolution pattern
- Add ServiceValidationConfig for validation parameters
- Add __init__ and __post_init__ methods to BaseGattService
- Make _info the single source of truth for uuid, name, summary
- Add get_class_uuid() class method for resolution without instantiation
- Refactor CustomBaseGattService with __init_subclass__ hook
- Add class-level _info attribute support with SIG override validation
- Add UnknownService class in base.py
- Update device.py UnknownService to work with new architecture
- Add is_sig_service() method to BluetoothUUID class
- All 805 tests pass, formatting and linting clean

Co-authored-by: RonanB96 <22995167+RonanB96@users.noreply.github.com>
Copilot AI changed the title [WIP] # Service Class Refactoring Service Class Refactoring - Align with Characteristic Architecture Oct 6, 2025
@RonanB96 RonanB96 marked this pull request as ready for review October 6, 2025 22:55
@RonanB96 RonanB96 requested a review from Copilot October 6, 2025 22:55
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 refactors all service classes to align with the established characteristic architecture pattern, removing legacy dataclass patterns and implementing a clean, maintainable design following established best practices.

Key Changes:

  • Removed @dataclass decorators from all service classes and implemented proper __init__ methods for BaseGattService
  • Added SIGServiceResolver class for centralized service resolution logic mirroring the characteristic pattern
  • Created CustomBaseGattService enhancements with __init_subclass__ hooks and class-level _info attribute support

Reviewed Changes

Copilot reviewed 17 out of 17 changed files in this pull request and generated 1 comment.

File Description
src/bluetooth_sig/types/uuid.py Added is_sig_service() method and SIG service UUID range constants
src/bluetooth_sig/gatt/services/base.py Major refactor: removed @DataClass, added SIGServiceResolver, new init patterns, CustomBaseGattService enhancements
src/bluetooth_sig/gatt/services/*.py Removed @DataClass decorators from all concrete service implementations
src/bluetooth_sig/device/device.py Updated UnknownService class to work with new service architecture

Comment thread src/bluetooth_sig/device/device.py Outdated
Copy link
Copy Markdown
Owner

@RonanB96 RonanB96 left a comment

Choose a reason for hiding this comment

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

@copilot review all comments in this thread and address them.
Also the timing test failed, it should not be too strict to get in the way

Comment thread src/bluetooth_sig/gatt/services/base.py Outdated
Comment thread src/bluetooth_sig/gatt/services/base.py Outdated
Comment thread src/bluetooth_sig/gatt/services/base.py Outdated
Comment thread src/bluetooth_sig/gatt/services/base.py Outdated
- Reorder service name resolution by hit rate: space-separated (64%) first, without-suffix (14%) second
- Remove unnecessary Progressive API Level comments
- Replace hasattr/getattr pattern with direct __dict__ check for _info attribute
- Relax timing test threshold from 1s to 2s to avoid flakiness
- All 805 tests pass, formatting and linting clean

Co-authored-by: RonanB96 <22995167+RonanB96@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Oct 7, 2025

@copilot review all comments in this thread and address them.
Also the timing test failed, it should not be too strict to get in the way

Addressed all review comments:

  1. Reordered service name resolution by measured hit rates (space-separated 64% first)
  2. Removed unnecessary Progressive API Level comments
  3. Fixed hasattr/getattr pattern to use direct dict check
  4. Relaxed timing test from 1s to 2s to avoid flakiness

All 805 tests pass, formatting and linting clean.

Copilot AI requested a review from RonanB96 October 7, 2025 15:03
@RonanB96 RonanB96 requested a review from Copilot October 7, 2025 17:44
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

Copilot reviewed 22 out of 22 changed files in this pull request and generated no new comments.

@RonanB96 RonanB96 merged commit cb33f56 into main Oct 7, 2025
4 checks passed
@RonanB96 RonanB96 deleted the copilot/fix-07a7a865-4acf-4700-8b09-8c2dc47d37dc branch October 7, 2025 18:17
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.

# Service Class Refactoring

3 participants