基于继承设计Python SOAP API包装器类,适配多动词方法
Great question! Building a maintainable SOAP API wrapper with reusable components is key to avoiding duplicate code, especially when dealing with consistent verb patterns and one-off special endpoints like singletons. Here's a structured approach using inheritance, mixins, and targeted overrides that fits your needs:
Start by creating an abstract base class that encapsulates all your standard verb operations (add, get, list, remove). This class will handle the generic SOAP call logic, so you only write it once. Subclasses just need to map to their specific SOAP method names.
from abc import ABC, abstractmethod class BaseSOAPResource(ABC): # Subclasses must define these to map to SOAP method names SOAP_ADD_METHOD = None SOAP_GET_METHOD = None SOAP_LIST_METHOD = None SOAP_REMOVE_METHOD = None def __init__(self, soap_client): self.soap_client = soap_client def _call_soap_method(self, method_name, **kwargs): # Generic SOAP call logic: invoke method, handle basic errors, parse response try: response = getattr(self.soap_client, method_name)(**kwargs) return self._parse_response(response) except AttributeError: raise ValueError(f"SOAP client does not support method: {method_name}") def _parse_response(self, response): # Default response parsing (convert SOAP types to Python objects, etc.) # Subclasses can override this for resource-specific parsing return response def add(self, **data): if not self.SOAP_ADD_METHOD: raise NotImplementedError("`add` is not supported for this resource") return self._call_soap_method(self.SOAP_ADD_METHOD, **data) def get(self, resource_id): if not self.SOAP_GET_METHOD: raise NotImplementedError("`get` is not supported for this resource") return self._call_soap_method(self.SOAP_GET_METHOD, id=resource_id) def list(self, **filters): if not self.SOAP_LIST_METHOD: raise NotImplementedError("`list` is not supported for this resource") return self._call_soap_method(self.SOAP_LIST_METHOD, **filters) def remove(self, resource_id): if not self.SOAP_REMOVE_METHOD: raise NotImplementedError("`remove` is not supported for this resource") return self._call_soap_method(self.SOAP_REMOVE_METHOD, id=resource_id)
For the extra methods like sync, options, apply, etc., use mixins. These are small, focused classes that add specific functionality without forcing all subclasses to inherit unused methods.
class SyncableMixin: SOAP_SYNC_METHOD = None def sync(self): if not self.SOAP_SYNC_METHOD: raise NotImplementedError("`sync` is not supported for this resource") return self._call_soap_method(self.SOAP_SYNC_METHOD) class OptionableMixin: SOAP_OPTIONS_METHOD = None def options(self, resource_id=None): if not self.SOAP_OPTIONS_METHOD: raise NotImplementedError("`options` is not supported for this resource") kwargs = {"id": resource_id} if resource_id else {} return self._call_soap_method(self.SOAP_OPTIONS_METHOD, **kwargs) class RestartableMixin: SOAP_RESTART_METHOD = None def restart(self): if not self.SOAP_RESTART_METHOD: raise NotImplementedError("`restart` is not supported for this resource") return self._call_soap_method(self.SOAP_RESTART_METHOD) # Add more mixins for `apply`, `reset`, etc., following the same pattern
Singletons (like system configuration, global settings) don’t fit the standard resource pattern—they don’t need list, get doesn’t require an ID, and remove is often disabled. Create a specialized base class that overrides the base behavior to match singleton needs:
class SingletonSOAPResource(BaseSOAPResource): # Singletons don't support listing, so disable by default SOAP_LIST_METHOD = None def get(self): # Singleton `get` doesn't need an ID—call the SOAP method directly if not self.SOAP_GET_METHOD: raise NotImplementedError("`get` is not supported for this singleton resource") return self._call_soap_method(self.SOAP_GET_METHOD) def remove(self): # Most singletons shouldn't be deletable; override to block or customize raise NotImplementedError("`remove` is not allowed for singleton resources") # Optional: Override `add` to act as an update (since singletons are created once) def add(self, **data): if not self.SOAP_ADD_METHOD: raise NotImplementedError("`update` is not supported for this singleton resource") # Rename in docs to clarify it's an update return self._call_soap_method(self.SOAP_ADD_METHOD, **data)
Regular Resource (e.g., Users)
Combine the base class with needed mixins:
class UserResource(BaseSOAPResource, SyncableMixin): SOAP_ADD_METHOD = "CreateUser" SOAP_GET_METHOD = "RetrieveUser" SOAP_LIST_METHOD = "ListAllUsers" SOAP_REMOVE_METHOD = "DeleteUser" SOAP_SYNC_METHOD = "SyncUserDirectory" # Initialize with your SOAP client soap_client = YourSOAPClient(endpoint="https://api.example.com/soap") user_api = UserResource(soap_client) # Use the methods new_user = user_api.add(name="Jane Doe", email="jane@example.com") active_users = user_api.list(status="active") user_api.sync()
Singleton Resource (e.g., System Config)
Use the singleton base class with relevant mixins:
class SystemConfigResource(SingletonSOAPResource, RestartableMixin, OptionableMixin): SOAP_GET_METHOD = "GetGlobalConfig" SOAP_ADD_METHOD = "UpdateGlobalConfig" # Maps to `add` which acts as update SOAP_RESTART_METHOD = "RestartSystemServices" SOAP_OPTIONS_METHOD = "GetConfigOptions" config_api = SystemConfigResource(soap_client) # Singleton-specific calls current_config = config_api.get() config_api.add(timezone="UTC", max_concurrent_users=50) config_api.restart()
- Max code reuse: Generic SOAP logic and verb implementations live in base classes, so you don’t repeat code across resources.
- Flexible composition: Mixins let you add only the extra methods a resource needs, avoiding bloated classes.
- Clear separation: Singleton resources have their own base class, making the API’s intent obvious to other developers.
- Easy extensibility: Add new verbs or optional methods by extending the base class or creating new mixins.
内容的提问来源于stack exchange,提问作者jonathan

