Skip to content

A decorator `@conditional_method` 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'.

License

Notifications You must be signed in to change notification settings

jymchng/conditional-method

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

39 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Conditional Method

[Repository is a Work In Progress]

Compatibility and Version

Python compat PyPi

CI/CD

Coverage

License and Issues

License Issues Closed Issues Open Issues

Development and Quality

Forks Stars Downloads Contributors Commits Last Commit Code Size Repo Size Watchers Activity PRs Merged PRs Open PRs

A powerful Python library that enables conditional method implementation based on runtime, initial conditions, at program startup or latest at class building time, allowing you to define different method implementations that are selected at when your classes are being built according to specific conditions.

πŸš€ Features

  • βœ… 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

πŸ“‹ Installation (Coming Soon!)

pip install conditional-method

πŸ”§ Usage of @conditional_method decorator (aliases: @if_, @cfg, @cm)

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'.

Basic Example

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)

Desugaring

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

@cfg can also be applied to global functions

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...")

@cfg_attr Decorator Usage

Conditionally Apply Decorators

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.

Basic Usage

@cfg_attr(condition=<boolean_expression>, decorators=[<decorator1>, <decorator2>, ...])
def my_function():
    # Decorators are applied in order when specified only when the `condition` is `True`

Key Features

  • Conditional Decoration: Apply decorators only when needed
  • Clean Code: Avoid cluttering your code with if/else branches

Examples

Feature Flags with Multiple Decorators

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.

πŸ” Debugging

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

πŸ“ Technical Details

The conditional_method decorator uses Python's descriptor protocol to manage method selection at runtime.

  1. Each method / global function / class decorated with @conditional_method is evaluated immediately by the decorator
  2. Only one method implementation (where condition is True) is bound to the class
  3. 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.
  4. If no conditions are True, an error is raised
  5. If multiple conditions are True, the LAST one encountered that is true is used
  6. Uses function qualnames to track different implementations of the same method
  7. Handles edge cases around method binding, descriptors, and garbage collection
  8. Clears the cache strategically to prevent memory leaks
  9. Supports both boolean conditions and callable conditions that evaluate at runtime

🀝 Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add some amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

πŸ“„ License

This project is licensed under the MIT License - see the LICENSE file for details.

πŸ™ Acknowledgements

  • Thanks to all contributors who have helped shape this project
  • Inspired by the need for cleaner conditional logic in Python applications

About

A decorator `@conditional_method` 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'.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published