Sea Python Library

repository·master·Indexed 41 days ago

https://github.com/shanbay/sea

A Python library for building applications with a structured framework. It features a CLI for running gRPC servers and custom jobs via @jobm, a configuration system driven by the SEA_ENV environment variable, and built-in support for Celery (AsyncTask and Bus) and Sentry. It includes utility data types like ImmutableDict and ConstantsObject, and provides a standardized pattern for creating and managing application extensions via BaseApp.

Tokens
9.9K
Snippets
41
Records
59
Agent score
100%

What's inside Sea

  1. Use built-in signals in sea

    master
    sea supports a signal system based on blinker. You can access built-in signals via the sea.signals module to hook into the lifecycle of the server and the application. These signals allow you to execute custom logic when specific lifecycle events occur.
  2. How middleware works in Sea

    master

    Sea uses a layered middleware architecture that wraps the gRPC service. Middleware is executed in a specific sequence: requests pass through the middleware stack in the order they are defined (e.g., Middleware 1 $\rightarrow$ Middleware 2 $\rightarrow$ gRPC Service), and responses pass back through the stack in reverse order (e.g., gRPC Service $\rightarrow$ Middleware 2 $\rightarrow$ Middleware 1).

    To enable middleware, add their names to the MIDDLEWARES list in your configuration. The order in this list determines the execution order.

    MIDDLEWARES = [
        'middleware 1',
        'middleware 2'
    ]
  3. Use `ImmutableDict` for immutable dictionaries

    master

    The ImmutableDict is a dictionary-like structure that prevents any modifications once it has been successfully initialized. It is useful for ensuring data integrity in parts of your application where configuration or state should not be changed.

    Key Behaviors

    • Immutability: Operations like .clear(), .pop(), or direct assignment (d['key'] = value) will raise a TypeError.
    • Copying:
      • Calling .copy() on an ImmutableDict returns a standard Python dict.
      • Using copy.copy() returns a new ImmutableDict instance.
    • Initialization: You can instantiate it with an existing dictionary or use the .fromkeys(keys, value) method to create one with uniform values.
    import datatypes
    import copy
    
    data = {'foo': 1, 'bar': 2, 'baz': 3}
    
    # Standard instantiation
    d1 = datatypes.ImmutableDict(data)
    
    # Creating a mutable copy (returns a standard dict)
    d2 = d1.copy()
    
    # Creating a new immutable copy (returns an ImmutableDict)
    d3 = copy.copy(d1)
    
    # Creating from keys with a default value
    d4 = datatypes.ImmutableDict.fromkeys(data.keys(), 'OK')
  4. How configuration is loaded via SEA_ENV

    master

    The project loads configuration from a Python module named configs located in the project root. The specific configuration module loaded depends on the SEA_ENV environment variable.

    By default, the configs module contains submodules for different environments, such as:

    • development
    • production
    • testing

    Important Constraints:

    • The environment name (the value of SEA_ENV) must not contain lowercase letters, otherwise the configuration will be ignored.
    • Environment variables with the same name as configuration keys have higher priority and will override the values defined in the configuration files (e.g., setting the environment variable GRPC_WORKERS will override the GRPC_WORKERS value in your configs module).
  5. Use `ConstantsObject` for attribute-based immutable objects

    master

    The ConstantsObject converts dictionary keys and values into object attributes. Once initialized, the attributes cannot be modified, making it ideal for read-only configuration objects that allow dot-notation access.

    Key Behaviors

    • Attribute Access: Access values using object.attribute instead of object['key'].
    • Immutability: Attempting to reassign an attribute (e.g., obj.foo = 2) will raise a TypeError.
    import datatypes
    
    data = {'foo': 1, 'bar': 2, 'baz': 3}
    d = datatypes.ConstantsObject(data)
    
    # Access via attribute
    print(d.foo)  # Output: 1
    
    # This will raise TypeError:
    d.foo = 2
  6. Understand the Sea project directory structure

    master

    Sea follows a "Convention over Configuration" philosophy. Every Sea project uses a fixed directory structure where specific functionalities are placed in dedicated modules. A standard project layout looks like this:

    .
    ├── app
    │   ├── extensions.py
    │   ├── __init__.py
    │   └── servicers.py
    ├── configs
    │   ├── default
    │   │   ├── __init__.py
    │   ├── development
    │   │   └── __init__.py
    │   ├── __init__.py
    │   └── testing
    │       └── __init__.py
    ├── jobs
    │   └── __init__.py
    ├── protos
    ├── requirements.txt
    └── tests
        └── __init__.py
  7. Register commands from third-party libraries

    master

    To make commands from a third-party library available via the sea CLI, register them in your library's setup.py using the sea.jobs entry point. This allows users to call your library's commands using sea <command_name> once your library is installed.

    Example: If your library sea_ext defines a command printf in sea_ext.cmds:

    # In your library's setup.py
    setup(
        ...
        entry_points={
            'sea.jobs': [
                'sayhello=sea_ext.cmds:printf',
            ]
        }
    )
  8. Quick Start: Integrate Sentry for event tracking

    master

    To enable Sentry event tracking in your project, follow these steps:

    1. Add raven to your project's dependencies.
    2. Set the SENTRY_DSN variable in your project configuration using your Sentry DSN URL.
    3. Register the Sentry extension in app/extensions.py.

    Once configured, the sentry instance will handle event tracking automatically.

    # 1. In your project configuration
    SENTRY_DSN = "https://username:token@sentry.io/1"
    
    # 2. In app/extensions.py
    from sea.contrib.extensions.sentry import Sentry
    
    sentry = Sentry()
  9. Write a custom extension

    master

    To create a new extension, define a class that implements the init_app(self, app) method. During the sea initialization process, the current_app instance is automatically passed as the app argument to this method.

    Implementation Pattern

    1. Define an __init__ method to set up initial state.
    2. Implement init_app(self, app) to configure the extension using app.config. Use app.config.get_namespace('PREFIX_') to retrieve a dictionary of configuration settings starting with a specific prefix.
    3. (Optional) Implement __getattr__ to proxy method calls from the extension instance directly to an underlying client object, making the extension transparent to the user.

    Example: Redis Extension

    This example creates a Redis extension that initializes a redis.StrictRedis object using configuration keys prefixed with REDIS_.

    import redis
    
    
    class Redis:
        def __init__(self):
            self._client = None
    
        def init_app(self, app):
            # Retrieves config keys starting with 'REDIS_'
            opts = app.config.get_namespace('REDIS_')
            self._pool = redis.ConnectionPool(**opts)
            self._client = redis.StrictRedis(connection_pool=self._pool)
    
        def __getattr__(self, name):
            # Proxies all calls to the underlying redis client
            return getattr(self._client, name)
  10. Use existing extensions in your application

    master

    To use an extension that has already been initialized, import the specific instance from app.extensions. This ensures you are using the singleton instance managed by the application lifecycle.

    1. Initialization (typically in app/extensions.py):

      from peeweext import Peeweext
      
      pw = Peeweext()
    2. Usage (in your business logic or routes):

      from app.extensions import pw
      
      pw.somethed()
    from app.extensions import pw
    
    pw.somethed()