- β Conditional Method Selection: Define methods that are only active when specific conditions are met
- π Runtime Conditional Logic: Choose method implementations based on runtime conditions
- π§© Clean Class Design: Keep your class definitions clean by separating conditional logic from implementation
- π¦ Type-Safe: Fully compatible with type checkers and includes type annotations
- π‘οΈ Zero Dependencies: No external dependencies required
- π Debuggable: Optional debug logging for troubleshooting
pip install conditional-method
A decorator @conditional_method
(aliases: @if_
, @cfg
, @cm
) that selects a method implementation (among those that are identically named) for a class during class build time, leaving only the selected method in its attributes set when the class is built, i.e. 'well-formed'.
from conditional_method import conditional_method
import os
ENVIRONMENT_KEY = "ENVIRONMENT_KEY"
os.environ[ENVIRONMENT_KEY] = "production"
ENVIRONMENT = os.environ[ENVIRONMENT_KEY]
class Worker:
__slots__ = ()
@conditional_method(condition=ENVIRONMENT == "production")
def work(self, *args, **kwargs):
print("Working in production")
print(f"Args: {args}")
print(f"Kwargs: {kwargs}")
return "production"
@conditional_method(condition=ENVIRONMENT == "development")
def work(self, *args, **kwargs):
print("Working in development")
print(f"Args: {args}")
print(f"Kwargs: {kwargs}")
return "development"
@conditional_method(condition=ENVIRONMENT == "staging")
def work(self, *args, **kwargs):
print("Working in staging")
print(f"Args: {args}")
print(f"Kwargs: {kwargs}")
return "staging"
worker = Worker()
print(f"ENVIRONMENT: {ENVIRONMENT}") # ENVIRONMENT: production
print(f"Worker.__dict__: {Worker.__dict__}")
# only one of the `work` methods will be bound to the Worker class
# Worker.__dict__: {
# '__module__': '__main__',
# '__slots__': (),
# 'work': <function Worker.work at 0x7f6151683670>,
# '__doc__': None
# }
print(worker.work(1, 2, 3, a=4, b=5))
# Working in production
# Args: (1, 2, 3)
# Kwargs: {'a': 4, 'b': 5}
# production (return value of the selected method)
The codes below:
import os
from conditional_method import conditional_method
class DatabaseConnector:
@conditional_method(condition=lambda f: os.environ.get("ENV") == "production")
def connect(self, config):
"""Used in production environment"""
print("Connecting to production database...")
# Production-specific connection logic
@conditional_method(condition=lambda f: os.environ.get("ENV") == "development")
def connect(self, config):
"""Used in development environment"""
print("Connecting to development database...")
# Development-specific connection logic
@conditional_method(condition=lambda f: os.environ.get("ENV") not in ["production", "development"])
def connect(self, config):
"""Used in any other environment"""
print("Connecting to local/test database...")
# Default connection logic
... basically desugars to:
import os
from conditional_method import conditional_method
class DatabaseConnector:
if os.environ.get("ENV") == "production":
def connect(self, config):
"""Used in production environment"""
print("Connecting to production database...")
# Production-specific connection logic
if os.environ.get("ENV") == "development":
def connect(self, config):
"""Used in development environment"""
print("Connecting to development database...")
# Development-specific connection logic
if not (os.environ.get("ENV") == "development" and os.environ.get("ENV") == "production"):
def connect(self, config):
"""Used in any other environment"""
print("Connecting to local/test database...")
# Default connection logic
import os
from conditional_method import conditional_method
@conditional_method(condition=os.environ.get("ENV") == "production")
def connect_to_db():
# Production implementation
print("Connecting to production database...")
@conditional_method(condition=os.environ.get("ENV") == "development")
def connect_to_db():
# Development implementation
print("Connecting to development database...")
The @cfg_attr
decorator enables conditional decorator(s) application based on configuration, environment variables, or any boolean condition. This powerful tool helps you write cleaner, more maintainable code by removing runtime conditionals and applying decorators selectively.
@cfg_attr(condition=<boolean_expression>, decorators=[<decorator1>, <decorator2>, ...])
def my_function():
# Decorators are applied in order when specified only when the `condition` is `True`
- Conditional Decoration: Apply decorators only when needed
- Clean Code: Avoid cluttering your code with if/else branches
Enable features conditionally and apply multiple decorators in one step:
os.environ["FEATURE_FLAG"] = "enabled" # Control with environment variables
@cfg_attr(
condition=os.environ.get("FEATURE_FLAG") == "enabled",
decorators=[log_calls, cache_result] # Apply both logging and caching
)
def experimental_feature(input_value):
print("Running experimental feature")
return f"Processed: {input_value}"
When the feature flag is enabled, both decorators are applied - logging function calls and caching results for better performance:
# First call - logged and cached
Calling experimental_feature with ('test_input',), {}
Running experimental feature
Function experimental_feature returned Processed: test_input
Result: Processed: test_input
# Second call - retrieves from cache
Result: Processed: test_input
Without changing your implementation, you can toggle features on and off or change how they're decorated simply by updating environment variables or other configuration.
When the feature flag is OFF, the function becomes un-callable at runtime as it will raise a TypeError
.
You can enable debug logging by setting the environment variable __conditional_method_debug__
to any value other than "false":
# Linux/macOS
export __conditional_method_debug__=true
# Windows
set __conditional_method_debug__=true
The conditional_method
decorator uses Python's descriptor protocol to manage method selection at runtime.
- Each method / global function / class decorated with
@conditional_method
is evaluated immediately by the decorator - Only one method implementation (where condition is
True
) is bound to the class - The class or global function can be treated as non-existent if the condition evaluates to
False
because the resultant class or function raises an exception on an attempt to create an instance of such class or to call the function. - If no conditions are
True
, an error is raised - If multiple conditions are
True
, the LAST one encountered that is true is used - Uses function qualnames to track different implementations of the same method
- Handles edge cases around method binding, descriptors, and garbage collection
- Clears the cache strategically to prevent memory leaks
- Supports both boolean conditions and callable conditions that evaluate at runtime
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature
) - Commit your changes (
git commit -m 'Add some amazing feature'
) - Push to the branch (
git push origin feature/amazing-feature
) - Open a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.
- Thanks to all contributors who have helped shape this project
- Inspired by the need for cleaner conditional logic in Python applications