From 684c722593dc3e7a95264386fc7368bf5ff20b53 Mon Sep 17 00:00:00 2001 From: Ryan Cartwright Date: Fri, 12 May 2023 18:51:42 +1000 Subject: [PATCH 1/2] fix subscriptions and add bucket notifications --- docs/nitric/api/documents.html | 2 +- docs/nitric/api/events.html | 2 +- docs/nitric/api/index.html | 9 +- docs/nitric/api/queues.html | 14 +- docs/nitric/api/secrets.html | 2 +- docs/nitric/api/storage.html | 96 +- docs/nitric/application.html | 17 +- docs/nitric/{api => }/exception.html | 154 ++-- docs/nitric/faas.html | 843 ++++++++++++++++-- docs/nitric/index.html | 5 + docs/nitric/proto/nitric/deploy/v1/index.html | 91 +- .../proto/nitric/document/v1/index.html | 18 +- docs/nitric/proto/nitric/error/v1/index.html | 4 +- docs/nitric/proto/nitric/faas/v1/index.html | 364 +++++++- docs/nitric/proto/nitric/index.html | 25 +- docs/nitric/proto/nitric/queue/v1/index.html | 10 +- .../proto/nitric/resource/v1/index.html | 27 +- .../nitric/proto/nitric/storage/v1/index.html | 8 +- docs/nitric/resources/apis.html | 115 ++- docs/nitric/resources/base.html | 17 +- docs/nitric/resources/buckets.html | 76 +- docs/nitric/resources/collections.html | 2 +- docs/nitric/resources/index.html | 177 ++-- docs/nitric/resources/queues.html | 2 +- docs/nitric/resources/schedules.html | 36 +- docs/nitric/resources/secrets.html | 2 +- docs/nitric/resources/topics.html | 47 +- makefile | 8 +- nitric/api/documents.py | 2 +- nitric/api/events.py | 2 +- nitric/api/queues.py | 2 +- nitric/api/secrets.py | 2 +- nitric/api/storage.py | 26 +- nitric/application.py | 3 +- nitric/{api => }/exception.py | 0 nitric/faas.py | 337 +++++-- nitric/proto/__init__.py | 18 + nitric/proto/nitric/__init__.py | 18 + nitric/proto/nitric/deploy/__init__.py | 18 + nitric/proto/nitric/deploy/v1/__init__.py | 29 +- nitric/proto/nitric/document/__init__.py | 18 + nitric/proto/nitric/document/v1/__init__.py | 19 + nitric/proto/nitric/error/__init__.py | 18 + nitric/proto/nitric/error/v1/__init__.py | 19 + nitric/proto/nitric/event/__init__.py | 18 + nitric/proto/nitric/event/v1/__init__.py | 19 + nitric/proto/nitric/faas/__init__.py | 18 + nitric/proto/nitric/faas/v1/__init__.py | 73 ++ nitric/proto/nitric/queue/__init__.py | 18 + nitric/proto/nitric/queue/v1/__init__.py | 19 + nitric/proto/nitric/resource/__init__.py | 18 + nitric/proto/nitric/resource/v1/__init__.py | 20 + nitric/proto/nitric/secret/__init__.py | 18 + nitric/proto/nitric/secret/v1/__init__.py | 19 + nitric/proto/nitric/storage/__init__.py | 18 + nitric/proto/nitric/storage/v1/__init__.py | 19 + nitric/proto/validate/__init__.py | 19 + nitric/resources/apis.py | 2 +- nitric/resources/base.py | 5 +- nitric/resources/buckets.py | 22 +- nitric/resources/collections.py | 2 +- nitric/resources/queues.py | 2 +- nitric/resources/secrets.py | 2 +- nitric/resources/topics.py | 15 +- tests/api/test_documents.py | 4 +- tests/api/test_events.py | 2 +- tests/api/test_queues.py | 2 +- tests/api/test_secrets.py | 2 +- tests/api/test_storage.py | 2 +- tests/resources/test_apis.py | 6 +- tests/test_application.py | 42 +- tests/{api => }/test_exception.py | 3 +- tests/test_faas.py | 124 ++- 73 files changed, 2642 insertions(+), 595 deletions(-) rename docs/nitric/{api => }/exception.html (73%) rename nitric/{api => }/exception.py (100%) rename tests/{api => }/test_exception.py (97%) diff --git a/docs/nitric/api/documents.html b/docs/nitric/api/documents.html index cba7aa9..feeba3c 100644 --- a/docs/nitric/api/documents.html +++ b/docs/nitric/api/documents.html @@ -53,7 +53,7 @@

Module nitric.api.documents

from grpclib import GRPCError from nitric.api.const import MAX_SUB_COLLECTION_DEPTH -from nitric.api.exception import exception_from_grpc_error +from nitric.exception import exception_from_grpc_error from nitric.proto.nitric.document.v1 import ( DocumentServiceStub, Collection as CollectionMessage, diff --git a/docs/nitric/api/events.html b/docs/nitric/api/events.html index aaca3c8..7f2ca8d 100644 --- a/docs/nitric/api/events.html +++ b/docs/nitric/api/events.html @@ -50,7 +50,7 @@

Module nitric.api.events

from grpclib import GRPCError -from nitric.api.exception import exception_from_grpc_error +from nitric.exception import exception_from_grpc_error from nitric.utils import new_default_channel, _struct_from_dict from nitric.proto.nitric.event.v1 import ( EventServiceStub, diff --git a/docs/nitric/api/index.html b/docs/nitric/api/index.html index 311470b..e83c603 100644 --- a/docs/nitric/api/index.html +++ b/docs/nitric/api/index.html @@ -80,10 +80,6 @@

Sub-modules

-
nitric.api.exception
-
-
-
nitric.api.queues
@@ -421,7 +417,7 @@

Methods

def bucket(self, name: str): """Return a reference to a bucket from the connected storage service.""" - return BucketRef(_storage=self, name=name) + return BucketRef(_storage=self, name=name, _server=None)

Methods

@@ -436,7 +432,7 @@

Methods

def bucket(self, name: str):
     """Return a reference to a bucket from the connected storage service."""
-    return BucketRef(_storage=self, name=name)
+ return BucketRef(_storage=self, name=name, _server=None)
@@ -586,7 +582,6 @@

Index

  • nitric.api.const
  • nitric.api.documents
  • nitric.api.events
  • -
  • nitric.api.exception
  • nitric.api.queues
  • nitric.api.secrets
  • nitric.api.storage
  • diff --git a/docs/nitric/api/queues.html b/docs/nitric/api/queues.html index 05867b7..c15b7a3 100644 --- a/docs/nitric/api/queues.html +++ b/docs/nitric/api/queues.html @@ -50,7 +50,7 @@

    Module nitric.api.queues

    from grpclib import GRPCError -from nitric.api.exception import FailedPreconditionException, exception_from_grpc_error, InvalidArgumentException +from nitric.exception import FailedPreconditionException, exception_from_grpc_error, InvalidArgumentException from nitric.utils import new_default_channel, _struct_from_dict, _dict_from_struct from nitric.proto.nitric.queue.v1 import ( QueueServiceStub, @@ -183,7 +183,9 @@

    Module nitric.api.queues

    task = Task() if isinstance(task, dict): - # TODO: handle tasks that are just a payload + # Handle if its just a payload + if task.get("payload") is None: + task = {"payload": task} task = Task(**task) try: @@ -332,7 +334,9 @@

    Class variables

    task = Task() if isinstance(task, dict): - # TODO: handle tasks that are just a payload + # Handle if its just a payload + if task.get("payload") is None: + task = {"payload": task} task = Task(**task) try: @@ -471,7 +475,9 @@

    Methods

    task = Task() if isinstance(task, dict): - # TODO: handle tasks that are just a payload + # Handle if its just a payload + if task.get("payload") is None: + task = {"payload": task} task = Task(**task) try: diff --git a/docs/nitric/api/secrets.html b/docs/nitric/api/secrets.html index 4391e42..05ecc18 100644 --- a/docs/nitric/api/secrets.html +++ b/docs/nitric/api/secrets.html @@ -50,7 +50,7 @@

    Module nitric.api.secrets

    from grpclib import GRPCError -from nitric.api.exception import exception_from_grpc_error +from nitric.exception import exception_from_grpc_error from nitric.utils import new_default_channel from nitric.proto.nitric.secret.v1 import ( SecretServiceStub, diff --git a/docs/nitric/api/storage.html b/docs/nitric/api/storage.html index 9ad109b..f082b67 100644 --- a/docs/nitric/api/storage.html +++ b/docs/nitric/api/storage.html @@ -45,9 +45,12 @@

    Module nitric.api.storage

    # limitations under the License. # from dataclasses import dataclass +from typing import Union from grpclib import GRPCError -from nitric.api.exception import exception_from_grpc_error, InvalidArgumentException +from nitric.exception import exception_from_grpc_error, InvalidArgumentException +from nitric.application import Nitric +from nitric.faas import FunctionServer, FileNotificationWorkerOptions, FileNotificationMiddleware from nitric.utils import new_default_channel from nitric.proto.nitric.storage.v1 import ( StorageServiceStub, @@ -59,6 +62,7 @@

    Module nitric.api.storage

    StorageListFilesRequest, ) from enum import Enum +from warnings import warn class Storage(object): @@ -80,15 +84,16 @@

    Module nitric.api.storage

    def bucket(self, name: str): """Return a reference to a bucket from the connected storage service.""" - return BucketRef(_storage=self, name=name) + return BucketRef(_storage=self, name=name, _server=None) -@dataclass(frozen=True, order=True) +@dataclass(order=True) class BucketRef(object): """A reference to a bucket in a storage service, used to the perform operations on that bucket.""" _storage: Storage name: str + _server: Union[FunctionServer, None] def file(self, key: str): """Return a reference to a file in this bucket.""" @@ -101,6 +106,22 @@

    Module nitric.api.storage

    ) return [self.file(f.key) for f in resp.files] + def on(self, notification_type: str, notification_prefix_filter: str): + """Create and return a bucket notification decorator for this bucket.""" + + def decorator(func: FileNotificationMiddleware): + self._server = FunctionServer( + FileNotificationWorkerOptions( + bucket=self, + notification_type=notification_type, + notification_prefix_filter=notification_prefix_filter, + ) + ) + self._server.bucket_notification(func) + Nitric._register_worker(self._server) + + return decorator + class FileMode(Enum): """Definition of available operation modes for file signed URLs.""" @@ -160,14 +181,15 @@

    Module nitric.api.storage

    async def upload_url(self, expiry: int = 600): """Get a temporary writable URL to this file.""" - await self.sign_url(mode=FileMode.WRITE, expiry=expiry) + return await self.sign_url(mode=FileMode.WRITE, expiry=expiry) async def download_url(self, expiry: int = 600): """Get a temporary readable URL to this file.""" - await self.sign_url(mode=FileMode.READ, expiry=expiry) + return await self.sign_url(mode=FileMode.READ, expiry=expiry) async def sign_url(self, mode: FileMode = FileMode.READ, expiry: int = 3600): """Generate a signed URL for reading or writing to a file.""" + warn("File.sign_url() is deprecated, use upload_url() or download_url() instead", DeprecationWarning) try: response = await self._storage._storage_stub.pre_sign_url( storage_pre_sign_url_request=StoragePreSignUrlRequest( @@ -190,7 +212,7 @@

    Classes

    class BucketRef -(_storage: Storage, name: str) +(_storage: Storage, name: str, _server: Optional[FunctionServer])

    A reference to a bucket in a storage service, used to the perform operations on that bucket.

    @@ -198,12 +220,13 @@

    Classes

    Expand source code -
    @dataclass(frozen=True, order=True)
    +
    @dataclass(order=True)
     class BucketRef(object):
         """A reference to a bucket in a storage service, used to the perform operations on that bucket."""
     
         _storage: Storage
         name: str
    +    _server: Union[FunctionServer, None]
     
         def file(self, key: str):
             """Return a reference to a file in this bucket."""
    @@ -214,7 +237,23 @@ 

    Classes

    resp = await self._storage._storage_stub.list_files( storage_list_files_request=StorageListFilesRequest(bucket_name=self.name) ) - return [self.file(f.key) for f in resp.files]
    + return [self.file(f.key) for f in resp.files] + + def on(self, notification_type: str, notification_prefix_filter: str): + """Create and return a bucket notification decorator for this bucket.""" + + def decorator(func: FileNotificationMiddleware): + self._server = FunctionServer( + FileNotificationWorkerOptions( + bucket=self, + notification_type=notification_type, + notification_prefix_filter=notification_prefix_filter, + ) + ) + self._server.bucket_notification(func) + Nitric._register_worker(self._server) + + return decorator

    Class variables

    @@ -256,6 +295,32 @@

    Methods

    return [self.file(f.key) for f in resp.files]
    +
    +def on(self, notification_type: str, notification_prefix_filter: str) +
    +
    +

    Create and return a bucket notification decorator for this bucket.

    +
    + +Expand source code + +
    def on(self, notification_type: str, notification_prefix_filter: str):
    +    """Create and return a bucket notification decorator for this bucket."""
    +
    +    def decorator(func: FileNotificationMiddleware):
    +        self._server = FunctionServer(
    +            FileNotificationWorkerOptions(
    +                bucket=self,
    +                notification_type=notification_type,
    +                notification_prefix_filter=notification_prefix_filter,
    +            )
    +        )
    +        self._server.bucket_notification(func)
    +        Nitric._register_worker(self._server)
    +
    +    return decorator
    +
    +
    @@ -310,14 +375,15 @@

    Methods

    async def upload_url(self, expiry: int = 600): """Get a temporary writable URL to this file.""" - await self.sign_url(mode=FileMode.WRITE, expiry=expiry) + return await self.sign_url(mode=FileMode.WRITE, expiry=expiry) async def download_url(self, expiry: int = 600): """Get a temporary readable URL to this file.""" - await self.sign_url(mode=FileMode.READ, expiry=expiry) + return await self.sign_url(mode=FileMode.READ, expiry=expiry) async def sign_url(self, mode: FileMode = FileMode.READ, expiry: int = 3600): """Generate a signed URL for reading or writing to a file.""" + warn("File.sign_url() is deprecated, use upload_url() or download_url() instead", DeprecationWarning) try: response = await self._storage._storage_stub.pre_sign_url( storage_pre_sign_url_request=StoragePreSignUrlRequest( @@ -367,7 +433,7 @@

    Methods

    async def download_url(self, expiry: int = 600):
         """Get a temporary readable URL to this file."""
    -    await self.sign_url(mode=FileMode.READ, expiry=expiry)
    + return await self.sign_url(mode=FileMode.READ, expiry=expiry)
    @@ -401,6 +467,7 @@

    Methods

    async def sign_url(self, mode: FileMode = FileMode.READ, expiry: int = 3600):
         """Generate a signed URL for reading or writing to a file."""
    +    warn("File.sign_url() is deprecated, use upload_url() or download_url() instead", DeprecationWarning)
         try:
             response = await self._storage._storage_stub.pre_sign_url(
                 storage_pre_sign_url_request=StoragePreSignUrlRequest(
    @@ -423,7 +490,7 @@ 

    Methods

    async def upload_url(self, expiry: int = 600):
         """Get a temporary writable URL to this file."""
    -    await self.sign_url(mode=FileMode.WRITE, expiry=expiry)
    + return await self.sign_url(mode=FileMode.WRITE, expiry=expiry)
    @@ -545,7 +612,7 @@

    Methods

    def bucket(self, name: str): """Return a reference to a bucket from the connected storage service.""" - return BucketRef(_storage=self, name=name)
    + return BucketRef(_storage=self, name=name, _server=None)

    Methods

    @@ -560,7 +627,7 @@

    Methods

    def bucket(self, name: str):
         """Return a reference to a bucket from the connected storage service."""
    -    return BucketRef(_storage=self, name=name)
    + return BucketRef(_storage=self, name=name, _server=None)
    @@ -587,6 +654,7 @@

    file
  • files
  • name
  • +
  • on
  • diff --git a/docs/nitric/application.html b/docs/nitric/application.html index 90f26a2..70463e8 100644 --- a/docs/nitric/application.html +++ b/docs/nitric/application.html @@ -54,7 +54,7 @@

    Module nitric.application

    from opentelemetry.instrumentation.grpc import GrpcInstrumentorClient from nitric.faas import FunctionServer -from nitric.api.exception import NitricUnavailableException +from nitric.exception import NitricUnavailableException # from nitric.resources.base import BaseResource from typing import Dict, List, Type, Any, TypeVar @@ -95,9 +95,9 @@

    Module nitric.application

    ) @classmethod - def _create_tracer(cls) -> TracerProvider: - local_run = "OTELCOL_BIN" not in environ - samplePercent = int(getenv("NITRIC_TRACE_SAMPLE_PERCENT", "100")) / 100.0 + def _create_tracer(cls, local: bool = True, sampler: int = 100) -> TracerProvider: + local_run = local or "OTELCOL_BIN" not in environ + samplePercent = int(getenv("NITRIC_TRACE_SAMPLE_PERCENT", sampler)) / 100.0 # If its a local run use a console exporter, otherwise export using OTEL Protocol exporter = OTLPSpanExporter(endpoint="http://localhost:4317", insecure=True) @@ -123,6 +123,7 @@

    Module nitric.application

    This will execute in an existing event loop if there is one, otherwise it will attempt to create its own. """ provider = cls._create_tracer() + print(cls._workers) try: try: loop = asyncio.get_running_loop() @@ -191,9 +192,9 @@

    Classes

    ) @classmethod - def _create_tracer(cls) -> TracerProvider: - local_run = "OTELCOL_BIN" not in environ - samplePercent = int(getenv("NITRIC_TRACE_SAMPLE_PERCENT", "100")) / 100.0 + def _create_tracer(cls, local: bool = True, sampler: int = 100) -> TracerProvider: + local_run = local or "OTELCOL_BIN" not in environ + samplePercent = int(getenv("NITRIC_TRACE_SAMPLE_PERCENT", sampler)) / 100.0 # If its a local run use a console exporter, otherwise export using OTEL Protocol exporter = OTLPSpanExporter(endpoint="http://localhost:4317", insecure=True) @@ -219,6 +220,7 @@

    Classes

    This will execute in an existing event loop if there is one, otherwise it will attempt to create its own. """ provider = cls._create_tracer() + print(cls._workers) try: try: loop = asyncio.get_running_loop() @@ -256,6 +258,7 @@

    Static methods

    This will execute in an existing event loop if there is one, otherwise it will attempt to create its own. """ provider = cls._create_tracer() + print(cls._workers) try: try: loop = asyncio.get_running_loop() diff --git a/docs/nitric/api/exception.html b/docs/nitric/exception.html similarity index 73% rename from docs/nitric/api/exception.html rename to docs/nitric/exception.html index adb743f..5ea7036 100644 --- a/docs/nitric/api/exception.html +++ b/docs/nitric/exception.html @@ -4,7 +4,7 @@ -nitric.api.exception API documentation +nitric.exception API documentation @@ -19,7 +19,7 @@
    -

    Module nitric.api.exception

    +

    Module nitric.exception

    @@ -228,7 +228,7 @@

    Module nitric.api.exception

    Functions

    -
    +
    def exception_from_grpc_code(code: int, message: str = None)
    @@ -250,7 +250,7 @@

    Functions

    return _exception_code_map[code](message)
    -
    +
    def exception_from_grpc_error(error: grpclib.exceptions.GRPCError)
    @@ -269,7 +269,7 @@

    Functions

    Classes

    -
    +
    class AbortedException (*args, **kwargs)
    @@ -286,12 +286,12 @@

    Classes

    Ancestors

    -
    +
    class AlreadyExistsException (*args, **kwargs)
    @@ -308,12 +308,12 @@

    Ancestors

    Ancestors

    -
    +
    class CancelledException (*args, **kwargs)
    @@ -330,12 +330,12 @@

    Ancestors

    Ancestors

    -
    +
    class DataLossException (*args, **kwargs)
    @@ -352,12 +352,12 @@

    Ancestors

    Ancestors

    -
    +
    class DeadlineExceededException (*args, **kwargs)
    @@ -374,12 +374,12 @@

    Ancestors

    Ancestors

    -
    +
    class FailedPreconditionException (*args, **kwargs)
    @@ -401,12 +401,12 @@

    Ancestors

    Ancestors

    -
    +
    class InternalException (*args, **kwargs)
    @@ -423,12 +423,12 @@

    Ancestors

    Ancestors

    -
    +
    class InvalidArgumentException (*args, **kwargs)
    @@ -452,12 +452,12 @@

    Ancestors

    Ancestors

    -
    +
    class NitricResourceException (*args, **kwargs)
    @@ -478,7 +478,7 @@

    Ancestors

  • builtins.BaseException
  • -
    +
    class NitricServiceException (*args, **kwargs)
    @@ -500,25 +500,25 @@

    Ancestors

    Subclasses

    -
    +
    class NitricUnavailableException (*args, **kwargs)
    @@ -539,7 +539,7 @@

    Ancestors

  • builtins.BaseException
  • -
    +
    class NotFoundException (*args, **kwargs)
    @@ -556,12 +556,12 @@

    Ancestors

    Ancestors

    -
    +
    class OutOfRangeException (*args, **kwargs)
    @@ -583,12 +583,12 @@

    Ancestors

    Ancestors

    -
    +
    class PermissionDeniedException (*args, **kwargs)
    @@ -605,12 +605,12 @@

    Ancestors

    Ancestors

    -
    +
    class ResourceExhaustedException (*args, **kwargs)
    @@ -627,12 +627,12 @@

    Ancestors

    Ancestors

    -
    +
    class UnauthenticatedException (*args, **kwargs)
    @@ -649,12 +649,12 @@

    Ancestors

    Ancestors

    -
    +
    class UnavailableException (*args, **kwargs)
    @@ -676,12 +676,12 @@

    Ancestors

    Ancestors

    -
    +
    class UnimplementedException (*args, **kwargs)
    @@ -703,12 +703,12 @@

    Ancestors

    Ancestors

    -
    +
    class UnknownException (*args, **kwargs)
    @@ -725,7 +725,7 @@

    Ancestors

    Ancestors

    @@ -741,73 +741,73 @@

    Index

    • Super-module

    • Functions

    • Classes

    • diff --git a/docs/nitric/faas.html b/docs/nitric/faas.html index 533e579..5d83ad5 100644 --- a/docs/nitric/faas.html +++ b/docs/nitric/faas.html @@ -69,6 +69,10 @@

      Module nitric.faas

      ApiWorker, SubscriptionWorker, ScheduleRate, + BucketNotificationWorker, + BucketNotificationConfig, + BucketNotificationType, + NotificationResponseContext, ) import grpclib import asyncio @@ -95,9 +99,10 @@

      Module nitric.faas

      class Request(ABC): """Represents an abstract trigger request.""" - def __init__(self, data: bytes): + def __init__(self, data: bytes, trace_context: Dict[str, str]): """Construct a new Request.""" self.data = data + self.trace_context = trace_context class Response(ABC): @@ -117,14 +122,23 @@

      Module nitric.faas

      """Return this context as an EventContext if it is one, otherwise returns None.""" return None + def bucket_notification(self) -> Union[BucketNotificationContext, None]: + """Return this context as a BucketNotificationContext if it is one, otherwise returns None.""" + return None + -def _ctx_from_grpc_trigger_request(trigger_request: TriggerRequest): +def _ctx_from_grpc_trigger_request(trigger_request: TriggerRequest, options: FaasWorkerOptions = None): """Return a TriggerContext from a TriggerRequest.""" - context_type, context = betterproto.which_one_of(trigger_request, "context") + context_type, _ = betterproto.which_one_of(trigger_request, "context") if context_type == "http": return HttpContext.from_grpc_trigger_request(trigger_request) elif context_type == "topic": return EventContext.from_grpc_trigger_request(trigger_request) + elif context_type == "notification": + if isinstance(options, FileNotificationWorkerOptions): + return FileNotificationContext.from_grpc_trigger_request(trigger_request, options) + else: + return BucketNotificationContext.from_grpc_trigger_request(trigger_request) else: print(f"Trigger with unknown context received, context type: {context_type}") raise Exception(f"Unknown trigger context, type: {context_type}") @@ -154,6 +168,9 @@

      Module nitric.faas

      success=ctx.res.success, ), ) + elif ctx.bucket_notification(): + ctx = ctx.bucket_notification() + return TriggerResponse(notification=NotificationResponseContext(success=ctx.res.success)) else: raise Exception("Unknown Trigger Context type, unable to return valid response") @@ -175,13 +192,12 @@

      Module nitric.faas

      trace_context: Dict[str, str], ): """Construct a new HttpRequest.""" - super().__init__(data) + super().__init__(data, trace_context) self.method = method self.path = path self.params = params self.query = query self.headers = headers - self.trace_context = trace_context @property def json(self) -> Optional[Any]: @@ -268,9 +284,8 @@

      Module nitric.faas

      def __init__(self, data: bytes, topic: str, trace_context: Dict[str, str]): """Construct a new EventRequest.""" - super().__init__(data) + super().__init__(data, trace_context) self.topic = topic - self.trace_context = trace_context @property def payload(self) -> bytes: @@ -311,83 +326,91 @@

      Module nitric.faas

      ) -# async def face(inpp: int) -> str: -# return "thing" +class BucketNotificationRequest(Request): + """Represents a translated Event, from a Subscribed Topic, forwarded from the Nitric Membrane.""" + def __init__(self, data: bytes, key: str, notification_type: BucketNotificationType, trace_context: Dict[str, str]): + """Construct a new EventRequest.""" + super().__init__(data, trace_context) -# ====== Function Handlers ====== + self.key = key + self.notification_type = notification_type -C = TypeVar("C", TriggerContext, HttpContext, EventContext) -Middleware = Callable -Handler = Coroutine[Any, Any, C] -HttpHandler = Coroutine[Any, Any, Optional[HttpContext]] -EventHandler = Coroutine[Any, Any, Optional[EventContext]] -Middleware = Callable[[C, Middleware], Handler] -HttpMiddleware = Callable[[HttpContext, HttpHandler], HttpHandler] -EventMiddleware = Callable[[EventContext, EventHandler], EventHandler] +class BucketNotificationResponse(Response): + """Represents the response to a trigger from a Bucket""" -def compose_middleware(*middlewares: Union[Middleware, List[Middleware]]) -> Middleware: - """ - Compose multiple middleware functions into a single middleware function. - - The resulting middleware will effectively be a chain of the provided middleware, - where each calls the next in the chain when they're successful. - """ - middlewares = list(middlewares) - if len(middlewares) == 1 and not isinstance(middlewares[0], list): - return middlewares[0] + def __init__(self, success: bool = True): + """Construct a new BucketNotificationResponse.""" + self.success = success - middlewares = [compose_middleware(m) if isinstance(m, list) else m for m in middlewares] - async def handler(ctx, next_middleware=lambda ctx: ctx): - def reduce_chain(acc_next, cur): - async def chained_middleware(context): - # Count the positional arguments to determine if the function is a handler or middleware. - all_args = cur.__code__.co_argcount - kwargs = len(cur.__defaults__) if cur.__defaults__ is not None else 0 - pos_args = all_args - kwargs - if pos_args == 2: - # Call the middleware with next and return the result - return ( - (await cur(context, acc_next)) if asyncio.iscoroutinefunction(cur) else cur(context, acc_next) - ) - else: - # Call the handler with ctx only, then call the remainder of the middleware chain - result = (await cur(context)) if asyncio.iscoroutinefunction(cur) else cur(context) - return (await acc_next(result)) if asyncio.iscoroutinefunction(acc_next) else acc_next(result) +class BucketNotificationContext(TriggerContext): + """Represents the full request/response context for a bucket notification trigger.""" - return chained_middleware + def __init__(self, request: BucketNotificationRequest, response: BucketNotificationResponse = None): + """Construct a new BucketNotificationContext.""" + super().__init__() + self.req = request + self.res = response if response else BucketNotificationResponse() - middleware_chain = functools.reduce(reduce_chain, reversed(middlewares + [next_middleware])) - return await middleware_chain(ctx) + def bucket_notification(self) -> BucketNotificationContext: + """Return this BucketNotificationContext, used when determining the context type of a trigger.""" + return self - return handler + @staticmethod + def from_grpc_trigger_request(trigger_request: TriggerRequest) -> BucketNotificationContext: + """Construct a new BucketNotificationContext from a Bucket Notification trigger from the Nitric Membrane.""" + return BucketNotificationContext( + request=BucketNotificationRequest( + data=trigger_request.data, + key=trigger_request.notification.bucket.key, + notification_type=trigger_request.notification.bucket.type, + trace_context=trigger_request.trace_context.values, + ) + ) -# ====== Function Server ====== +class FileNotificationRequest(BucketNotificationRequest): + def __init__( + self, + data: bytes, + bucket_ref: Any, # can't import BucketRef due to circular dependency problems + key: str, + notification_type: BucketNotificationType, + trace_context: Dict[str, str], + ): + super().__init__(data=data, key=key, notification_type=notification_type, trace_context=trace_context) + self.file = bucket_ref.file(key) -def _create_internal_error_response(req: TriggerRequest) -> TriggerResponse: - """Create a general error response based on the trigger request type.""" - context_type, context = betterproto.which_one_of(req, "context") - if context_type == "http": - return TriggerResponse(data=bytes(), http=HttpResponseContext(status=500)) - elif context_type == "topic": - return TriggerResponse(data=bytes(), topic=TopicResponseContext(success=False)) - else: - raise Exception(f"Unknown trigger type: {context_type}, unable to generate expected response") +class FileNotificationContext(TriggerContext): + """Represents the full request/response context for an Http based trigger.""" + def __init__(self, request: FileNotificationRequest, response: BucketNotificationResponse = None): + """Construct a new FileNotificationContext.""" + super().__init__() + self.req = request + self.res = response if response else BucketNotificationResponse() -class ApiWorkerOptions: - """Options for API workers.""" + def bucket_notification(self) -> FileNotificationContext: + """Return this FileNotificationContext, used when determining the context type of a trigger.""" + return self - def __init__(self, api: str, route: str, methods: List[Union[str, HttpMethod]], opts: MethodOptions): - """Construct a new options object.""" - self.api = api - self.route = route - self.methods = [str(method) for method in methods] - self.opts = opts + @staticmethod + def from_grpc_trigger_request( + trigger_request: TriggerRequest, options: FileNotificationWorkerOptions + ) -> FileNotificationContext: + """Construct a new FileNotificationTrigger from a Bucket Notification trigger from the Nitric Membrane.""" + return FileNotificationContext( + request=FileNotificationRequest( + data=trigger_request.data, + key=trigger_request.notification.bucket.key, + bucket_ref=options.bucket_ref, + notification_type=trigger_request.notification.bucket.type, + trace_context=trigger_request.trace_context.values, + ) + ) class RateWorkerOptions: @@ -404,6 +427,55 @@

      Module nitric.faas

      self.frequency = frequency +class BucketNotificationWorkerOptions: + """Options for bucket notification workers.""" + + def __init__(self, bucket_name: str, notification_type: str, notification_prefix_filter: str): + """Construct a new options object.""" + self.bucket_name = bucket_name + self.notification_type = BucketNotificationWorkerOptions._to_grpc_event_type(notification_type.lower()) + self.notification_prefix_filter = notification_prefix_filter + + @staticmethod + def _to_grpc_event_type(event_type: str) -> BucketNotificationType: + if event_type == "write": + return BucketNotificationType.Created + elif event_type == "delete": + return BucketNotificationType.Deleted + else: + raise ValueError(f"Event type {event_type} is unsupported") + + +class FileNotificationWorkerOptions(BucketNotificationWorkerOptions): + """Options for bucket notification workers with file references.""" + + def __init__(self, bucket, notification_type: str, notification_prefix_filter: str): + super().__init__(bucket.name, notification_type, notification_prefix_filter) + + self.bucket_ref = bucket + + +class ApiWorkerOptions: + """Options for API workers.""" + + def __init__(self, api: str, route: str, methods: List[Union[str, HttpMethod]], opts: MethodOptions): + """Construct a new options object.""" + self.api = api + self.route = route + self.methods = [str(method) for method in methods] + self.opts = opts + + +class MethodOptions: + """Represents options when defining a method handler.""" + + security: dict[str, List[str]] + + def __init__(self, security: dict[str, List[str]] = None): + """Construct a new HTTP method options object.""" + self.security = security + + class SubscriptionWorkerOptions: """Options for subscription workers.""" @@ -423,7 +495,10 @@

      Module nitric.faas

      @staticmethod def from_str(value: str) -> Frequency: """Convert a string frequency value to a Frequency.""" - return Frequency[value.strip().lower()] + try: + return Frequency[value.strip().lower()] + except Exception: + raise ValueError(f"{value} is not valid frequency") @staticmethod def as_str_list() -> List[str]: @@ -431,23 +506,88 @@

      Module nitric.faas

      return [str(frequency.value) for frequency in Frequency] -class MethodOptions: - """Represents options when defining a method handler.""" +class FaasWorkerOptions: + """Empty worker options for generic function handlers.""" - security: dict[str, List[str]] + pass - def __init__(self, security: dict[str, List[str]] = None): - """Construct a new HTTP method options object.""" - self.security = security +FaasClientOptions = Union[ + ApiWorkerOptions, + RateWorkerOptions, + SubscriptionWorkerOptions, + BucketNotificationWorkerOptions, + FileNotificationWorkerOptions, + FaasWorkerOptions, +] -class FaasWorkerOptions: - """Empty worker options for generic function handlers.""" - pass +# Defining Function Handlers and Middleware +C = TypeVar("C", TriggerContext, HttpContext, EventContext) +Middleware = Callable +Handler = Coroutine[Any, Any, C] +HttpHandler = Coroutine[Any, Any, Optional[HttpContext]] +EventHandler = Coroutine[Any, Any, Optional[EventContext]] +BucketNotificationHandler = Coroutine[Any, Any, Optional[BucketNotificationContext]] +FileNotificationHandler = Coroutine[Any, Any, Optional[FileNotificationContext]] +Middleware = Callable[[C, Middleware], Handler] +HttpMiddleware = Callable[[HttpContext, HttpHandler], HttpHandler] +EventMiddleware = Callable[[EventContext, EventHandler], EventHandler] +BucketNotificationMiddleware = Callable[ + [BucketNotificationContext, BucketNotificationHandler], BucketNotificationHandler +] +FileNotificationMiddleware = Callable[[FileNotificationContext, FileNotificationHandler], FileNotificationHandler] -FaasClientOptions = Union[ApiWorkerOptions, RateWorkerOptions, SubscriptionWorkerOptions, FaasWorkerOptions] + +def compose_middleware(*middlewares: Union[Middleware, List[Middleware]]) -> Middleware: + """ + Compose multiple middleware functions into a single middleware function. + + The resulting middleware will effectively be a chain of the provided middleware, + where each calls the next in the chain when they're successful. + """ + middlewares = list(middlewares) + if len(middlewares) == 1 and not isinstance(middlewares[0], list): + return middlewares[0] + + middlewares = [compose_middleware(m) if isinstance(m, list) else m for m in middlewares] + + async def handler(ctx, next_middleware=lambda ctx: ctx): + def reduce_chain(acc_next, cur): + async def chained_middleware(mw_ctx): + # Count the positional arguments to determine if the function is a handler or middleware. + all_args = cur.__code__.co_argcount + kwargs = len(cur.__defaults__) if cur.__defaults__ is not None else 0 + pos_args = all_args - kwargs + if pos_args == 2: + # Call the middleware with next and return the result + return (await cur(mw_ctx, acc_next)) if asyncio.iscoroutinefunction(cur) else cur(mw_ctx, acc_next) + else: + # Call the handler with ctx only, then call the remainder of the middleware chain + result = (await cur(mw_ctx)) if asyncio.iscoroutinefunction(cur) else cur(mw_ctx) + return (await acc_next(result)) if asyncio.iscoroutinefunction(acc_next) else acc_next(result) + + return chained_middleware + + middleware_chain = functools.reduce(reduce_chain, reversed(middlewares + [next_middleware])) + return await middleware_chain(ctx) + + return handler + + +# ====== Function Server ====== + + +def _create_internal_error_response(req: TriggerRequest) -> TriggerResponse: + """Create a general error response based on the trigger request type.""" + context_type, ctx = betterproto.which_one_of(req, "context") + if context_type == "http": + return TriggerResponse(data=bytes(), http=HttpResponseContext(status=500)) + elif context_type == "topic": + return TriggerResponse(data=bytes(), topic=TopicResponseContext(success=False)) + else: + raise Exception(f"Unknown trigger type: {context_type}, unable to generate expected response") class FunctionServer: @@ -457,6 +597,7 @@

      Module nitric.faas

      """Construct a new function server.""" self.__http_handler = None self.__event_handler = None + self.__bucket_notification_handler = None self._any_handler = None self._opts = opts @@ -478,10 +619,24 @@

      Module nitric.faas

      self.__event_handler = compose_middleware(*handlers) return self + def bucket_notification(self, *handlers: Union[Middleware, List[Middleware]]) -> FunctionServer: + """ + Register one or more Bucket Notification Trigger Handlers or Middleware. + + When multiple handlers are provided, they will be called in order. + """ + self.__bucket_notification_handler = compose_middleware(*handlers) + return self + async def start(self, *handlers: Union[Middleware, List[Middleware]]): """Start the function server using the provided trigger handlers.""" self._any_handler = compose_middleware(*handlers) if len(handlers) > 0 else None - if not self._any_handler and not self._http_handler and not self._event_handler: + if ( + not self._any_handler + and not self._http_handler + and not self._event_handler + and not self.__bucket_notification_handler + ): raise Exception("At least one handler function must be provided.") await self._run() @@ -494,6 +649,10 @@

      Module nitric.faas

      def _event_handler(self): return self.__event_handler if self.__event_handler else self._any_handler + @property + def _bucket_notification_handler(self): + return self.__bucket_notification_handler if self.__bucket_notification_handler else self._any_handler + async def _run(self): """Register a new FaaS worker with the Membrane, using the provided function as the handler.""" channel = new_default_channel() @@ -516,6 +675,16 @@

      Module nitric.faas

      ) elif isinstance(self._opts, SubscriptionWorkerOptions): init_request = InitRequest(subscription=SubscriptionWorker(topic=self._opts.topic)) + elif isinstance(self._opts, BucketNotificationWorkerOptions) or isinstance( + self._opts, FileNotificationWorkerOptions + ): + config = BucketNotificationConfig( + notification_type=self._opts.notification_type, + notification_prefix_filter=self._opts.notification_prefix_filter, + ) + init_request = InitRequest( + bucket_notification=BucketNotificationWorker(bucket=self._opts.bucket_name, config=config) + ) # let the membrane server know we're ready to start await request_channel.send(ClientMessage(init_request=init_request)) @@ -529,7 +698,7 @@

      Module nitric.faas

      # proceed to the next available message continue if msg_type == "trigger_request": - ctx = _ctx_from_grpc_trigger_request(srv_msg.trigger_request) + ctx = _ctx_from_grpc_trigger_request(srv_msg.trigger_request, self._opts) try: if len(ctx.req.trace_context) > 0: @@ -539,6 +708,8 @@

      Module nitric.faas

      func = self._http_handler elif ctx.event(): func = self._event_handler + elif ctx.bucket_notification(): + func = self._bucket_notification_handler else: func = self._any_handler @@ -604,6 +775,15 @@

      Module nitric.faas

      return FunctionServer(opts=[]).event(*handlers) +def bucket_notification(*handlers: Union[Middleware, List[Middleware]]) -> FunctionServer: + """ + Create a new Function Server and Register one or more Bucket Notification Handlers or Middleware. + + When multiple handlers are provided, they will be called in order. + """ + return FunctionServer(opts=[]).bucket_notification(*handlers) + + def start(*handlers: Union[Middleware, List[Middleware]]): """Create a new Function Server and start it using the provided trigger handlers.""" if len(handlers) < 1: @@ -618,6 +798,25 @@

      Module nitric.faas

      Functions

      +
      +def bucket_notification(*handlers: Union[Middleware, List[Middleware]]) ‑> FunctionServer +
      +
      +

      Create a new Function Server and Register one or more Bucket Notification Handlers or Middleware.

      +

      When multiple handlers are provided, they will be called in order.

      +
      + +Expand source code + +
      def bucket_notification(*handlers: Union[Middleware, List[Middleware]]) -> FunctionServer:
      +    """
      +    Create a new Function Server and Register one or more Bucket Notification Handlers or Middleware.
      +
      +    When multiple handlers are provided, they will be called in order.
      +    """
      +    return FunctionServer(opts=[]).bucket_notification(*handlers)
      +
      +
      def compose_middleware(*middlewares: Union[Middleware, List[Middleware]]) ‑> Callable[[~C, Callable], Coroutine[Any, Any, ~C]]
      @@ -644,19 +843,17 @@

      Functions

      async def handler(ctx, next_middleware=lambda ctx: ctx): def reduce_chain(acc_next, cur): - async def chained_middleware(context): + async def chained_middleware(mw_ctx): # Count the positional arguments to determine if the function is a handler or middleware. all_args = cur.__code__.co_argcount kwargs = len(cur.__defaults__) if cur.__defaults__ is not None else 0 pos_args = all_args - kwargs if pos_args == 2: # Call the middleware with next and return the result - return ( - (await cur(context, acc_next)) if asyncio.iscoroutinefunction(cur) else cur(context, acc_next) - ) + return (await cur(mw_ctx, acc_next)) if asyncio.iscoroutinefunction(cur) else cur(mw_ctx, acc_next) else: # Call the handler with ctx only, then call the remainder of the middleware chain - result = (await cur(context)) if asyncio.iscoroutinefunction(cur) else cur(context) + result = (await cur(mw_ctx)) if asyncio.iscoroutinefunction(cur) else cur(mw_ctx) return (await acc_next(result)) if asyncio.iscoroutinefunction(acc_next) else acc_next(result) return chained_middleware @@ -748,6 +945,188 @@

      Classes

      self.opts = opts
      +
      +class BucketNotificationContext +(request: BucketNotificationRequest, response: BucketNotificationResponse = None) +
      +
      +

      Represents the full request/response context for a bucket notification trigger.

      +

      Construct a new BucketNotificationContext.

      +
      + +Expand source code + +
      class BucketNotificationContext(TriggerContext):
      +    """Represents the full request/response context for a bucket notification trigger."""
      +
      +    def __init__(self, request: BucketNotificationRequest, response: BucketNotificationResponse = None):
      +        """Construct a new BucketNotificationContext."""
      +        super().__init__()
      +        self.req = request
      +        self.res = response if response else BucketNotificationResponse()
      +
      +    def bucket_notification(self) -> BucketNotificationContext:
      +        """Return this BucketNotificationContext, used when determining the context type of a trigger."""
      +        return self
      +
      +    @staticmethod
      +    def from_grpc_trigger_request(trigger_request: TriggerRequest) -> BucketNotificationContext:
      +        """Construct a new BucketNotificationContext from a Bucket Notification trigger from the Nitric Membrane."""
      +        return BucketNotificationContext(
      +            request=BucketNotificationRequest(
      +                data=trigger_request.data,
      +                key=trigger_request.notification.bucket.key,
      +                notification_type=trigger_request.notification.bucket.type,
      +                trace_context=trigger_request.trace_context.values,
      +            )
      +        )
      +
      +

      Ancestors

      + +

      Static methods

      +
      +
      +def from_grpc_trigger_request(trigger_request: TriggerRequest) ‑> BucketNotificationContext +
      +
      +

      Construct a new BucketNotificationContext from a Bucket Notification trigger from the Nitric Membrane.

      +
      + +Expand source code + +
      @staticmethod
      +def from_grpc_trigger_request(trigger_request: TriggerRequest) -> BucketNotificationContext:
      +    """Construct a new BucketNotificationContext from a Bucket Notification trigger from the Nitric Membrane."""
      +    return BucketNotificationContext(
      +        request=BucketNotificationRequest(
      +            data=trigger_request.data,
      +            key=trigger_request.notification.bucket.key,
      +            notification_type=trigger_request.notification.bucket.type,
      +            trace_context=trigger_request.trace_context.values,
      +        )
      +    )
      +
      +
      +
      +

      Methods

      +
      +
      +def bucket_notification(self) ‑> BucketNotificationContext +
      +
      +

      Return this BucketNotificationContext, used when determining the context type of a trigger.

      +
      + +Expand source code + +
      def bucket_notification(self) -> BucketNotificationContext:
      +    """Return this BucketNotificationContext, used when determining the context type of a trigger."""
      +    return self
      +
      +
      +
      +

      Inherited members

      + +
      +
      +class BucketNotificationRequest +(data: bytes, key: str, notification_type: BucketNotificationType, trace_context: Dict[str, str]) +
      +
      +

      Represents a translated Event, from a Subscribed Topic, forwarded from the Nitric Membrane.

      +

      Construct a new EventRequest.

      +
      + +Expand source code + +
      class BucketNotificationRequest(Request):
      +    """Represents a translated Event, from a Subscribed Topic, forwarded from the Nitric Membrane."""
      +
      +    def __init__(self, data: bytes, key: str, notification_type: BucketNotificationType, trace_context: Dict[str, str]):
      +        """Construct a new EventRequest."""
      +        super().__init__(data, trace_context)
      +
      +        self.key = key
      +        self.notification_type = notification_type
      +
      +

      Ancestors

      + +

      Subclasses

      + +
      +
      +class BucketNotificationResponse +(success: bool = True) +
      +
      +

      Represents the response to a trigger from a Bucket

      +

      Construct a new BucketNotificationResponse.

      +
      + +Expand source code + +
      class BucketNotificationResponse(Response):
      +    """Represents the response to a trigger from a Bucket"""
      +
      +    def __init__(self, success: bool = True):
      +        """Construct a new BucketNotificationResponse."""
      +        self.success = success
      +
      +

      Ancestors

      + +
      +
      +class BucketNotificationWorkerOptions +(bucket_name: str, notification_type: str, notification_prefix_filter: str) +
      +
      +

      Options for bucket notification workers.

      +

      Construct a new options object.

      +
      + +Expand source code + +
      class BucketNotificationWorkerOptions:
      +    """Options for bucket notification workers."""
      +
      +    def __init__(self, bucket_name: str, notification_type: str, notification_prefix_filter: str):
      +        """Construct a new options object."""
      +        self.bucket_name = bucket_name
      +        self.notification_type = BucketNotificationWorkerOptions._to_grpc_event_type(notification_type.lower())
      +        self.notification_prefix_filter = notification_prefix_filter
      +
      +    @staticmethod
      +    def _to_grpc_event_type(event_type: str) -> BucketNotificationType:
      +        if event_type == "write":
      +            return BucketNotificationType.Created
      +        elif event_type == "delete":
      +            return BucketNotificationType.Deleted
      +        else:
      +            raise ValueError(f"Event type {event_type} is unsupported")
      +
      +

      Subclasses

      + +
      class EventContext (request: EventRequest, response: EventResponse = None) @@ -833,6 +1212,7 @@

      Inherited members

      • TriggerContext:
      • @@ -854,9 +1234,8 @@

        Inherited members

        def __init__(self, data: bytes, topic: str, trace_context: Dict[str, str]): """Construct a new EventRequest.""" - super().__init__(data) + super().__init__(data, trace_context) self.topic = topic - self.trace_context = trace_context @property def payload(self) -> bytes: @@ -924,6 +1303,159 @@

        Ancestors

        pass
        +
        +class FileNotificationContext +(request: FileNotificationRequest, response: BucketNotificationResponse = None) +
        +
        +

        Represents the full request/response context for an Http based trigger.

        +

        Construct a new FileNotificationContext.

        +
        + +Expand source code + +
        class FileNotificationContext(TriggerContext):
        +    """Represents the full request/response context for an Http based trigger."""
        +
        +    def __init__(self, request: FileNotificationRequest, response: BucketNotificationResponse = None):
        +        """Construct a new FileNotificationContext."""
        +        super().__init__()
        +        self.req = request
        +        self.res = response if response else BucketNotificationResponse()
        +
        +    def bucket_notification(self) -> FileNotificationContext:
        +        """Return this FileNotificationContext, used when determining the context type of a trigger."""
        +        return self
        +
        +    @staticmethod
        +    def from_grpc_trigger_request(
        +        trigger_request: TriggerRequest, options: FileNotificationWorkerOptions
        +    ) -> FileNotificationContext:
        +        """Construct a new FileNotificationTrigger from a Bucket Notification trigger from the Nitric Membrane."""
        +        return FileNotificationContext(
        +            request=FileNotificationRequest(
        +                data=trigger_request.data,
        +                key=trigger_request.notification.bucket.key,
        +                bucket_ref=options.bucket_ref,
        +                notification_type=trigger_request.notification.bucket.type,
        +                trace_context=trigger_request.trace_context.values,
        +            )
        +        )
        +
        +

        Ancestors

        + +

        Static methods

        +
        +
        +def from_grpc_trigger_request(trigger_request: TriggerRequest, options: FileNotificationWorkerOptions) ‑> FileNotificationContext +
        +
        +

        Construct a new FileNotificationTrigger from a Bucket Notification trigger from the Nitric Membrane.

        +
        + +Expand source code + +
        @staticmethod
        +def from_grpc_trigger_request(
        +    trigger_request: TriggerRequest, options: FileNotificationWorkerOptions
        +) -> FileNotificationContext:
        +    """Construct a new FileNotificationTrigger from a Bucket Notification trigger from the Nitric Membrane."""
        +    return FileNotificationContext(
        +        request=FileNotificationRequest(
        +            data=trigger_request.data,
        +            key=trigger_request.notification.bucket.key,
        +            bucket_ref=options.bucket_ref,
        +            notification_type=trigger_request.notification.bucket.type,
        +            trace_context=trigger_request.trace_context.values,
        +        )
        +    )
        +
        +
        +
        +

        Methods

        +
        +
        +def bucket_notification(self) ‑> FileNotificationContext +
        +
        +

        Return this FileNotificationContext, used when determining the context type of a trigger.

        +
        + +Expand source code + +
        def bucket_notification(self) -> FileNotificationContext:
        +    """Return this FileNotificationContext, used when determining the context type of a trigger."""
        +    return self
        +
        +
        +
        +

        Inherited members

        + +
        +
        +class FileNotificationRequest +(data: bytes, bucket_ref: Any, key: str, notification_type: BucketNotificationType, trace_context: Dict[str, str]) +
        +
        +

        Represents a translated Event, from a Subscribed Topic, forwarded from the Nitric Membrane.

        +

        Construct a new EventRequest.

        +
        + +Expand source code + +
        class FileNotificationRequest(BucketNotificationRequest):
        +    def __init__(
        +        self,
        +        data: bytes,
        +        bucket_ref: Any,  # can't import BucketRef due to circular dependency problems
        +        key: str,
        +        notification_type: BucketNotificationType,
        +        trace_context: Dict[str, str],
        +    ):
        +        super().__init__(data=data, key=key, notification_type=notification_type, trace_context=trace_context)
        +        self.file = bucket_ref.file(key)
        +
        +

        Ancestors

        + +
        +
        +class FileNotificationWorkerOptions +(bucket, notification_type: str, notification_prefix_filter: str) +
        +
        +

        Options for bucket notification workers with file references.

        +

        Construct a new options object.

        +
        + +Expand source code + +
        class FileNotificationWorkerOptions(BucketNotificationWorkerOptions):
        +    """Options for bucket notification workers with file references."""
        +
        +    def __init__(self, bucket, notification_type: str, notification_prefix_filter: str):
        +        super().__init__(bucket.name, notification_type, notification_prefix_filter)
        +
        +        self.bucket_ref = bucket
        +
        +

        Ancestors

        + +
        class Frequency (value, names=None, *, module=None, qualname=None, type=None, start=1) @@ -945,7 +1477,10 @@

        Ancestors

        @staticmethod def from_str(value: str) -> Frequency: """Convert a string frequency value to a Frequency.""" - return Frequency[value.strip().lower()] + try: + return Frequency[value.strip().lower()] + except Exception: + raise ValueError(f"{value} is not valid frequency") @staticmethod def as_str_list() -> List[str]: @@ -1004,7 +1539,10 @@

        Static methods

        @staticmethod
         def from_str(value: str) -> Frequency:
             """Convert a string frequency value to a Frequency."""
        -    return Frequency[value.strip().lower()]
        + try: + return Frequency[value.strip().lower()] + except Exception: + raise ValueError(f"{value} is not valid frequency")
      @@ -1027,6 +1565,7 @@

      Static methods

      """Construct a new function server.""" self.__http_handler = None self.__event_handler = None + self.__bucket_notification_handler = None self._any_handler = None self._opts = opts @@ -1048,10 +1587,24 @@

      Static methods

      self.__event_handler = compose_middleware(*handlers) return self + def bucket_notification(self, *handlers: Union[Middleware, List[Middleware]]) -> FunctionServer: + """ + Register one or more Bucket Notification Trigger Handlers or Middleware. + + When multiple handlers are provided, they will be called in order. + """ + self.__bucket_notification_handler = compose_middleware(*handlers) + return self + async def start(self, *handlers: Union[Middleware, List[Middleware]]): """Start the function server using the provided trigger handlers.""" self._any_handler = compose_middleware(*handlers) if len(handlers) > 0 else None - if not self._any_handler and not self._http_handler and not self._event_handler: + if ( + not self._any_handler + and not self._http_handler + and not self._event_handler + and not self.__bucket_notification_handler + ): raise Exception("At least one handler function must be provided.") await self._run() @@ -1064,6 +1617,10 @@

      Static methods

      def _event_handler(self): return self.__event_handler if self.__event_handler else self._any_handler + @property + def _bucket_notification_handler(self): + return self.__bucket_notification_handler if self.__bucket_notification_handler else self._any_handler + async def _run(self): """Register a new FaaS worker with the Membrane, using the provided function as the handler.""" channel = new_default_channel() @@ -1086,6 +1643,16 @@

      Static methods

      ) elif isinstance(self._opts, SubscriptionWorkerOptions): init_request = InitRequest(subscription=SubscriptionWorker(topic=self._opts.topic)) + elif isinstance(self._opts, BucketNotificationWorkerOptions) or isinstance( + self._opts, FileNotificationWorkerOptions + ): + config = BucketNotificationConfig( + notification_type=self._opts.notification_type, + notification_prefix_filter=self._opts.notification_prefix_filter, + ) + init_request = InitRequest( + bucket_notification=BucketNotificationWorker(bucket=self._opts.bucket_name, config=config) + ) # let the membrane server know we're ready to start await request_channel.send(ClientMessage(init_request=init_request)) @@ -1099,7 +1666,7 @@

      Static methods

      # proceed to the next available message continue if msg_type == "trigger_request": - ctx = _ctx_from_grpc_trigger_request(srv_msg.trigger_request) + ctx = _ctx_from_grpc_trigger_request(srv_msg.trigger_request, self._opts) try: if len(ctx.req.trace_context) > 0: @@ -1109,6 +1676,8 @@

      Static methods

      func = self._http_handler elif ctx.event(): func = self._event_handler + elif ctx.bucket_notification(): + func = self._bucket_notification_handler else: func = self._any_handler @@ -1154,6 +1723,26 @@

      Static methods

      Methods

      +
      +def bucket_notification(self, *handlers: Union[Middleware, List[Middleware]]) ‑> FunctionServer +
      +
      +

      Register one or more Bucket Notification Trigger Handlers or Middleware.

      +

      When multiple handlers are provided, they will be called in order.

      +
      + +Expand source code + +
      def bucket_notification(self, *handlers: Union[Middleware, List[Middleware]]) -> FunctionServer:
      +    """
      +    Register one or more Bucket Notification Trigger Handlers or Middleware.
      +
      +    When multiple handlers are provided, they will be called in order.
      +    """
      +    self.__bucket_notification_handler = compose_middleware(*handlers)
      +    return self
      +
      +
      def event(self, *handlers: Union[Middleware, List[Middleware]]) ‑> FunctionServer
      @@ -1206,7 +1795,12 @@

      Methods

      async def start(self, *handlers: Union[Middleware, List[Middleware]]):
           """Start the function server using the provided trigger handlers."""
           self._any_handler = compose_middleware(*handlers) if len(handlers) > 0 else None
      -    if not self._any_handler and not self._http_handler and not self._event_handler:
      +    if (
      +        not self._any_handler
      +        and not self._http_handler
      +        and not self._event_handler
      +        and not self.__bucket_notification_handler
      +    ):
               raise Exception("At least one handler function must be provided.")
       
           await self._run()
      @@ -1327,6 +1921,7 @@

      Inherited members

      • TriggerContext:
      • @@ -1412,13 +2007,12 @@

        Class variables

        trace_context: Dict[str, str], ): """Construct a new HttpRequest.""" - super().__init__(data) + super().__init__(data, trace_context) self.method = method self.path = path self.params = params self.query = query self.headers = headers - self.trace_context = trace_context @property def json(self) -> Optional[Any]: @@ -1602,7 +2196,7 @@

        Class variables

        class Request -(data: bytes) +(data: bytes, trace_context: Dict[str, str])

        Represents an abstract trigger request.

        @@ -1614,9 +2208,10 @@

        Class variables

        class Request(ABC):
             """Represents an abstract trigger request."""
         
        -    def __init__(self, data: bytes):
        +    def __init__(self, data: bytes, trace_context: Dict[str, str]):
                 """Construct a new Request."""
        -        self.data = data
        + self.data = data + self.trace_context = trace_context

        Ancestors

          @@ -1624,6 +2219,7 @@

          Ancestors

        Subclasses

        @@ -1648,6 +2244,7 @@

        Ancestors

      Subclasses

      @@ -1689,6 +2286,10 @@

      Subclasses

      def event(self) -> Union[EventContext, None]: """Return this context as an EventContext if it is one, otherwise returns None.""" + return None + + def bucket_notification(self) -> Union[BucketNotificationContext, None]: + """Return this context as a BucketNotificationContext if it is one, otherwise returns None.""" return None

      Ancestors

      @@ -1697,11 +2298,27 @@

      Ancestors

    Subclasses

    Methods

    +
    +def bucket_notification(self) ‑> Optional[BucketNotificationContext] +
    +
    +

    Return this context as a BucketNotificationContext if it is one, otherwise returns None.

    +
    + +Expand source code + +
    def bucket_notification(self) -> Union[BucketNotificationContext, None]:
    +    """Return this context as a BucketNotificationContext if it is one, otherwise returns None."""
    +    return None
    +
    +
    def event(self) ‑> Optional[EventContext]
    @@ -1748,6 +2365,7 @@

    Index

  • Functions

  • -

    Api(openapi: str = )

    +

    Api(openapi: str = )

    Expand source code @@ -415,22 +423,63 @@

    Class variables

    class Bucket +(notifications: List[ForwardRef('BucketNotificationTarget')] = <object object>)
    -

    Bucket()

    +

    Bucket(notifications: List[ForwardRef('BucketNotificationTarget')] = )

    Expand source code
    @dataclass(eq=False, repr=False)
     class Bucket(betterproto.Message):
    -    pass
    + notifications: List["BucketNotificationTarget"] = betterproto.message_field(1) +
    +

    Ancestors

    +
      +
    • betterproto.Message
    • +
    • abc.ABC
    • +
    +

    Class variables

    +
    +
    var notifications : List[BucketNotificationTarget]
    +
    +
    +
    +
    + +
    +class BucketNotificationTarget +(config: __faas_v1__.BucketNotificationConfig = <object object>, execution_unit: str = <object object>) +
    +
    +

    BucketNotificationTarget(config: 'faas_v1.BucketNotificationConfig' = , execution_unit: str = )

    +
    + +Expand source code + +
    @dataclass(eq=False, repr=False)
    +class BucketNotificationTarget(betterproto.Message):
    +    config: "__faas_v1__.BucketNotificationConfig" = betterproto.message_field(1)
    +    execution_unit: str = betterproto.string_field(2, group="target")
    +    """The name of an execution unit to target"""

    Ancestors

    • betterproto.Message
    • abc.ABC
    +

    Class variables

    +
    +
    var configBucketNotificationConfig
    +
    +
    +
    +
    var execution_unit : str
    +
    +

    The name of an execution unit to target

    +
    +
    class Collection @@ -456,7 +505,7 @@

    Ancestors

    (message: DeployEventMessage = <object object>, result: DeployDownEventResult = <object object>)
    -

    DeployDownEvent(message: 'DeployEventMessage' = , result: 'DeployDownEventResult' = )

    +

    DeployDownEvent(message: 'DeployEventMessage' = , result: 'DeployDownEventResult' = )

    Expand source code @@ -509,7 +558,7 @@

    Ancestors

    (attributes: betterproto_lib_google_protobuf.Struct = <object object>)
    -

    DeployDownRequest(attributes: 'betterproto_lib_google_protobuf.Struct' = )

    +

    DeployDownRequest(attributes: 'betterproto_lib_google_protobuf.Struct' = )

    Expand source code @@ -777,7 +826,7 @@

    Methods

    (message: DeployEventMessage = <object object>, result: DeployUpEventResult = <object object>)
    -

    DeployUpEvent(message: 'DeployEventMessage' = , result: 'DeployUpEventResult' = )

    +

    DeployUpEvent(message: 'DeployEventMessage' = , result: 'DeployUpEventResult' = )

    Expand source code @@ -850,7 +899,7 @@

    Class variables

    (spec: Spec = <object object>, attributes: betterproto_lib_google_protobuf.Struct = <object object>)
    -

    DeployUpRequest(spec: 'Spec' = , attributes: 'betterproto_lib_google_protobuf.Struct' = )

    +

    DeployUpRequest(spec: 'Spec' = , attributes: 'betterproto_lib_google_protobuf.Struct' = )

    Expand source code @@ -1073,7 +1122,7 @@

    Ancestors

    (name: str = <object object>, type: __resource_v1__.ResourceType = <object object>, execution_unit: ExecutionUnit = <object object>, bucket: Bucket = <object object>, topic: Topic = <object object>, queue: Queue = <object object>, api: Api = <object object>, policy: Policy = <object object>, schedule: Schedule = <object object>, collection: Collection = <object object>, secret: Secret = <object object>)
    -

    Resource(name: str = , type: 'resource_v1.ResourceType' = , execution_unit: 'ExecutionUnit' = , bucket: 'Bucket' = , topic: 'Topic' = , queue: 'Queue' = , api: 'Api' = , policy: 'Policy' = , schedule: 'Schedule' = , collection: 'Collection' = , secret: 'Secret' = )

    +

    Resource(name: str = , type: 'resource_v1.ResourceType' = , execution_unit: 'ExecutionUnit' = , bucket: 'Bucket' = , topic: 'Topic' = , queue: 'Queue' = , api: 'Api' = , policy: 'Policy' = , schedule: 'Schedule' = , collection: 'Collection' = , secret: 'Secret' = )

    Expand source code @@ -1150,7 +1199,7 @@

    Class variables

    (cron: str = <object object>, target: ScheduleTarget = <object object>)
    -

    Schedule(cron: str = , target: 'ScheduleTarget' = )

    +

    Schedule(cron: str = , target: 'ScheduleTarget' = )

    Expand source code @@ -1182,7 +1231,7 @@

    Class variables

    (execution_unit: str = <object object>)
    -

    ScheduleTarget(execution_unit: str = )

    +

    ScheduleTarget(execution_unit: str = )

    Expand source code @@ -1229,7 +1278,7 @@

    Ancestors

    (resources: List[ForwardRef('Resource')] = <object object>)
    -

    Spec(resources: List[ForwardRef('Resource')] = )

    +

    Spec(resources: List[ForwardRef('Resource')] = )

    Expand source code @@ -1257,7 +1306,7 @@

    Class variables

    (execution_unit: str = <object object>)
    -

    SubscriptionTarget(execution_unit: str = )

    +

    SubscriptionTarget(execution_unit: str = )

    Expand source code @@ -1285,7 +1334,7 @@

    Class variables

    (subscriptions: List[ForwardRef('SubscriptionTarget')] = <object object>)
    -

    Topic(subscriptions: List[ForwardRef('SubscriptionTarget')] = )

    +

    Topic(subscriptions: List[ForwardRef('SubscriptionTarget')] = )

    Expand source code @@ -1313,7 +1362,7 @@

    Class variables

    (target: SubscriptionTarget = <object object>)
    -

    TopicSubscription(target: 'SubscriptionTarget' = )

    +

    TopicSubscription(target: 'SubscriptionTarget' = )

    Expand source code @@ -1340,7 +1389,7 @@

    Class variables

    (string_result: str = <object object>)
    -

    UpResult(string_result: str = )

    +

    UpResult(string_result: str = )

    Expand source code @@ -1387,6 +1436,16 @@

    Bucket

    + + +
  • +

    BucketNotificationTarget

    +
  • Collection

    diff --git a/docs/nitric/proto/nitric/document/v1/index.html b/docs/nitric/proto/nitric/document/v1/index.html index e961349..ef13347 100644 --- a/docs/nitric/proto/nitric/document/v1/index.html +++ b/docs/nitric/proto/nitric/document/v1/index.html @@ -502,7 +502,7 @@

    Class variables

    (key: Key = <object object>)
    -

    DocumentDeleteRequest(key: 'Key' = )

    +

    DocumentDeleteRequest(key: 'Key' = )

    Expand source code @@ -549,7 +549,7 @@

    Ancestors

    (key: Key = <object object>)
    -

    DocumentGetRequest(key: 'Key' = )

    +

    DocumentGetRequest(key: 'Key' = )

    Expand source code @@ -577,7 +577,7 @@

    Class variables

    (document: Document = <object object>)
    -

    DocumentGetResponse(document: 'Document' = )

    +

    DocumentGetResponse(document: 'Document' = )

    Expand source code @@ -605,7 +605,7 @@

    Class variables

    (collection: Collection = <object object>, expressions: List[ForwardRef('Expression')] = <object object>, limit: int = <object object>, paging_token: Dict[str, str] = <object object>)
    -

    DocumentQueryRequest(collection: 'Collection' = , expressions: List[ForwardRef('Expression')] = , limit: int = , paging_token: Dict[str, str] = )

    +

    DocumentQueryRequest(collection: 'Collection' = , expressions: List[ForwardRef('Expression')] = , limit: int = , paging_token: Dict[str, str] = )

    Expand source code @@ -656,7 +656,7 @@

    Class variables

    (documents: List[ForwardRef('Document')] = <object object>, paging_token: Dict[str, str] = <object object>)
    -

    DocumentQueryResponse(documents: List[ForwardRef('Document')] = , paging_token: Dict[str, str] = )

    +

    DocumentQueryResponse(documents: List[ForwardRef('Document')] = , paging_token: Dict[str, str] = )

    Expand source code @@ -697,7 +697,7 @@

    Class variables

    (collection: Collection = <object object>, expressions: List[ForwardRef('Expression')] = <object object>, limit: int = <object object>)
    -

    DocumentQueryStreamRequest(collection: 'Collection' = , expressions: List[ForwardRef('Expression')] = , limit: int = )

    +

    DocumentQueryStreamRequest(collection: 'Collection' = , expressions: List[ForwardRef('Expression')] = , limit: int = )

    Expand source code @@ -739,7 +739,7 @@

    Class variables

    (document: Document = <object object>)
    -

    DocumentQueryStreamResponse(document: 'Document' = )

    +

    DocumentQueryStreamResponse(document: 'Document' = )

    Expand source code @@ -1203,7 +1203,7 @@

    Methods

    (key: Key = <object object>, content: betterproto_lib_google_protobuf.Struct = <object object>)
    -

    DocumentSetRequest(key: 'Key' = , content: 'betterproto_lib_google_protobuf.Struct' = )

    +

    DocumentSetRequest(key: 'Key' = , content: 'betterproto_lib_google_protobuf.Struct' = )

    Expand source code @@ -1301,7 +1301,7 @@

    Class variables

    (int_value: int = <object object>, double_value: float = <object object>, string_value: str = <object object>, bool_value: bool = <object object>)
    -

    ExpressionValue(int_value: int = , double_value: float = , string_value: str = , bool_value: bool = )

    +

    ExpressionValue(int_value: int = , double_value: float = , string_value: str = , bool_value: bool = )

    Expand source code diff --git a/docs/nitric/proto/nitric/error/v1/index.html b/docs/nitric/proto/nitric/error/v1/index.html index 30322c5..908e0fd 100644 --- a/docs/nitric/proto/nitric/error/v1/index.html +++ b/docs/nitric/proto/nitric/error/v1/index.html @@ -98,7 +98,7 @@

    Classes

    (message: str = <object object>, cause: str = <object object>, scope: ErrorScope = <object object>)
    -

    ErrorDetails(message: str = , cause: str = , scope: 'ErrorScope' = )

    +

    ErrorDetails(message: str = , cause: str = , scope: 'ErrorScope' = )

    Expand source code @@ -142,7 +142,7 @@

    Class variables

    (service: str = <object object>, plugin: str = <object object>, args: Dict[str, str] = <object object>)
    -

    ErrorScope(service: str = , plugin: str = , args: Dict[str, str] = )

    +

    ErrorScope(service: str = , plugin: str = , args: Dict[str, str] = )

    Expand source code diff --git a/docs/nitric/proto/nitric/faas/v1/index.html b/docs/nitric/proto/nitric/faas/v1/index.html index d54803c..6e63451 100644 --- a/docs/nitric/proto/nitric/faas/v1/index.html +++ b/docs/nitric/proto/nitric/faas/v1/index.html @@ -72,6 +72,14 @@

    Module nitric.proto.nitric.faas.v1

    from grpclib.metadata import Deadline +class BucketNotificationType(betterproto.Enum): + """Notification Workers""" + + All = 0 + Created = 1 + Deleted = 2 + + @dataclass(eq=False, repr=False) class ClientMessage(betterproto.Message): """Messages the client is able to send to the server""" @@ -155,6 +163,24 @@

    Module nitric.proto.nitric.faas.v1

    cron: str = betterproto.string_field(1) +@dataclass(eq=False, repr=False) +class BucketNotificationWorker(betterproto.Message): + bucket: str = betterproto.string_field(1) + config: "BucketNotificationConfig" = betterproto.message_field(2) + + +@dataclass(eq=False, repr=False) +class BucketNotificationConfig(betterproto.Message): + notification_type: "BucketNotificationType" = betterproto.enum_field(1) + notification_prefix_filter: str = betterproto.string_field(2) + """ + A notification filter is a prefix for a bucket object in which creations or + deletions should trigger a notification: e.g. Notification filter: + /images/cat and Event Type: created, would trigger on creating + /images/cat.png and /images/cat.jpg but not creating /cat.png + """ + + @dataclass(eq=False, repr=False) class InitRequest(betterproto.Message): """ @@ -166,6 +192,9 @@

    Module nitric.proto.nitric.faas.v1

    api: "ApiWorker" = betterproto.message_field(10, group="Worker") subscription: "SubscriptionWorker" = betterproto.message_field(11, group="Worker") schedule: "ScheduleWorker" = betterproto.message_field(12, group="Worker") + bucket_notification: "BucketNotificationWorker" = betterproto.message_field( + 13, group="Worker" + ) @dataclass(eq=False, repr=False) @@ -202,6 +231,9 @@

    Module nitric.proto.nitric.faas.v1

    http: "HttpTriggerContext" = betterproto.message_field(3, group="context") topic: "TopicTriggerContext" = betterproto.message_field(4, group="context") + notification: "NotificationTriggerContext" = betterproto.message_field( + 5, group="context" + ) @dataclass(eq=False, repr=False) @@ -271,6 +303,18 @@

    Module nitric.proto.nitric.faas.v1

    """The topic the message was published for""" +@dataclass(eq=False, repr=False) +class BucketNotification(betterproto.Message): + key: str = betterproto.string_field(1) + type: "BucketNotificationType" = betterproto.enum_field(2) + + +@dataclass(eq=False, repr=False) +class NotificationTriggerContext(betterproto.Message): + source: str = betterproto.string_field(1) + bucket: "BucketNotification" = betterproto.message_field(10, group="notification") + + @dataclass(eq=False, repr=False) class TriggerResponse(betterproto.Message): """The worker has successfully processed a trigger""" @@ -284,6 +328,11 @@

    Module nitric.proto.nitric.faas.v1

    topic: "TopicResponseContext" = betterproto.message_field(11, group="context") """response to a topic trigger""" + notification: "NotificationResponseContext" = betterproto.message_field( + 12, group="context" + ) + """response to a notification trigger""" + @dataclass(eq=False, repr=False) class HttpResponseContext(betterproto.Message): @@ -324,6 +373,11 @@

    Module nitric.proto.nitric.faas.v1

    """Success status of the handled event""" +@dataclass(eq=False, repr=False) +class NotificationResponseContext(betterproto.Message): + success: bool = betterproto.bool_field(1) + + class FaasServiceStub(betterproto.ServiceStub): async def trigger_stream( self, @@ -388,7 +442,7 @@

    Classes

    (api: str = <object object>, path: str = <object object>, methods: List[str] = <object object>, options: ApiWorkerOptions = <object object>)
    -

    ApiWorker(api: str = , path: str = , methods: List[str] = , options: 'ApiWorkerOptions' = )

    +

    ApiWorker(api: str = , path: str = , methods: List[str] = , options: 'ApiWorkerOptions' = )

    Expand source code @@ -430,7 +484,7 @@

    Class variables

    (security: Dict[str, ForwardRef('ApiWorkerScopes')] = <object object>, security_disabled: bool = <object object>)
    -

    ApiWorkerOptions(security: Dict[str, ForwardRef('ApiWorkerScopes')] = , security_disabled: bool = )

    +

    ApiWorkerOptions(security: Dict[str, ForwardRef('ApiWorkerScopes')] = , security_disabled: bool = )

    Expand source code @@ -473,7 +527,7 @@

    Class variables

    (scopes: List[str] = <object object>)
    -

    ApiWorkerScopes(scopes: List[str] = )

    +

    ApiWorkerScopes(scopes: List[str] = )

    Expand source code @@ -495,6 +549,151 @@

    Class variables

    +
    +class BucketNotification +(key: str = <object object>, type: BucketNotificationType = <object object>) +
    +
    +

    BucketNotification(key: str = , type: 'BucketNotificationType' = )

    +
    + +Expand source code + +
    @dataclass(eq=False, repr=False)
    +class BucketNotification(betterproto.Message):
    +    key: str = betterproto.string_field(1)
    +    type: "BucketNotificationType" = betterproto.enum_field(2)
    +
    +

    Ancestors

    +
      +
    • betterproto.Message
    • +
    • abc.ABC
    • +
    +

    Class variables

    +
    +
    var key : str
    +
    +
    +
    +
    var typeBucketNotificationType
    +
    +
    +
    +
    + +
    +class BucketNotificationConfig +(notification_type: BucketNotificationType = <object object>, notification_prefix_filter: str = <object object>) +
    +
    +

    BucketNotificationConfig(notification_type: 'BucketNotificationType' = , notification_prefix_filter: str = )

    +
    + +Expand source code + +
    @dataclass(eq=False, repr=False)
    +class BucketNotificationConfig(betterproto.Message):
    +    notification_type: "BucketNotificationType" = betterproto.enum_field(1)
    +    notification_prefix_filter: str = betterproto.string_field(2)
    +    """
    +    A notification filter is a prefix for a bucket object in which creations or
    +    deletions should trigger a notification: e.g. Notification filter:
    +    /images/cat and Event Type: created, would trigger on creating
    +    /images/cat.png and /images/cat.jpg but not creating /cat.png
    +    """
    +
    +

    Ancestors

    +
      +
    • betterproto.Message
    • +
    • abc.ABC
    • +
    +

    Class variables

    +
    +
    var notification_prefix_filter : str
    +
    +

    A notification filter is a prefix for a bucket object in which creations or +deletions should trigger a notification: e.g. Notification filter: +/images/cat and Event Type: created, would trigger on creating +/images/cat.png and /images/cat.jpg but not creating /cat.png

    +
    +
    var notification_typeBucketNotificationType
    +
    +
    +
    +
    + +
    +class BucketNotificationType +(value, names=None, *, module=None, qualname=None, type=None, start=1) +
    +
    +

    Notification Workers

    +
    + +Expand source code + +
    class BucketNotificationType(betterproto.Enum):
    +    """Notification Workers"""
    +
    +    All = 0
    +    Created = 1
    +    Deleted = 2
    +
    +

    Ancestors

    +
      +
    • betterproto.Enum
    • +
    • enum.IntEnum
    • +
    • builtins.int
    • +
    • enum.Enum
    • +
    +

    Class variables

    +
    +
    var All
    +
    +
    +
    +
    var Created
    +
    +
    +
    +
    var Deleted
    +
    +
    +
    +
    +
    +
    +class BucketNotificationWorker +(bucket: str = <object object>, config: BucketNotificationConfig = <object object>) +
    +
    +

    BucketNotificationWorker(bucket: str = , config: 'BucketNotificationConfig' = )

    +
    + +Expand source code + +
    @dataclass(eq=False, repr=False)
    +class BucketNotificationWorker(betterproto.Message):
    +    bucket: str = betterproto.string_field(1)
    +    config: "BucketNotificationConfig" = betterproto.message_field(2)
    +
    +

    Ancestors

    +
      +
    • betterproto.Message
    • +
    • abc.ABC
    • +
    +

    Class variables

    +
    +
    var bucket : str
    +
    +
    +
    +
    var configBucketNotificationConfig
    +
    +
    +
    +
    +
    class ClientMessage (id: str = <object object>, init_request: InitRequest = <object object>, trigger_response: TriggerResponse = <object object>) @@ -679,7 +878,7 @@

    Methods

    (value: List[str] = <object object>)
    -

    HeaderValue(value: List[str] = )

    +

    HeaderValue(value: List[str] = )

    Expand source code @@ -765,7 +964,7 @@

    Class variables

    (method: str = <object object>, path: str = <object object>, headers_old: Dict[str, str] = <object object>, query_params_old: Dict[str, str] = <object object>, headers: Dict[str, ForwardRef('HeaderValue')] = <object object>, query_params: Dict[str, ForwardRef('QueryValue')] = <object object>, path_params: Dict[str, str] = <object object>)
    -

    HttpTriggerContext(method: str = , path: str = , headers_old: Dict[str, str] = , query_params_old: Dict[str, str] = , headers: Dict[str, ForwardRef('HeaderValue')] = , query_params: Dict[str, ForwardRef('QueryValue')] = , path_params: Dict[str, str] = )

    +

    HttpTriggerContext(method: str = , path: str = , headers_old: Dict[str, str] = , query_params_old: Dict[str, str] = , headers: Dict[str, ForwardRef('HeaderValue')] = , query_params: Dict[str, ForwardRef('QueryValue')] = , path_params: Dict[str, str] = )

    Expand source code @@ -861,7 +1060,7 @@

    Class variables

    class InitRequest -(api: ApiWorker = <object object>, subscription: SubscriptionWorker = <object object>, schedule: ScheduleWorker = <object object>) +(api: ApiWorker = <object object>, subscription: SubscriptionWorker = <object object>, schedule: ScheduleWorker = <object object>, bucket_notification: BucketNotificationWorker = <object object>)

    InitRequest - Identifies a worker as ready to recieve triggers This message @@ -881,7 +1080,10 @@

    Class variables

    api: "ApiWorker" = betterproto.message_field(10, group="Worker") subscription: "SubscriptionWorker" = betterproto.message_field(11, group="Worker") - schedule: "ScheduleWorker" = betterproto.message_field(12, group="Worker") + schedule: "ScheduleWorker" = betterproto.message_field(12, group="Worker") + bucket_notification: "BucketNotificationWorker" = betterproto.message_field( + 13, group="Worker" + )

    Ancestors

    +
    +class NotificationResponseContext +(success: bool = <object object>) +
    +
    +

    NotificationResponseContext(success: bool = )

    +
    + +Expand source code + +
    @dataclass(eq=False, repr=False)
    +class NotificationResponseContext(betterproto.Message):
    +    success: bool = betterproto.bool_field(1)
    +
    +

    Ancestors

    +
      +
    • betterproto.Message
    • +
    • abc.ABC
    • +
    +

    Class variables

    +
    +
    var success : bool
    +
    +
    +
    +
    + +
    +class NotificationTriggerContext +(source: str = <object object>, bucket: BucketNotification = <object object>) +
    +
    +

    NotificationTriggerContext(source: str = , bucket: 'BucketNotification' = )

    +
    + +Expand source code + +
    @dataclass(eq=False, repr=False)
    +class NotificationTriggerContext(betterproto.Message):
    +    source: str = betterproto.string_field(1)
    +    bucket: "BucketNotification" = betterproto.message_field(10, group="notification")
    +
    +

    Ancestors

    +
      +
    • betterproto.Message
    • +
    • abc.ABC
    • +
    +

    Class variables

    +
    +
    var bucketBucketNotification
    +
    +
    +
    +
    var source : str
    +
    +
    +
    +
    +
    class QueryValue (value: List[str] = <object object>)
    -

    QueryValue(value: List[str] = )

    +

    QueryValue(value: List[str] = )

    Expand source code @@ -957,7 +1222,7 @@

    Class variables

    (cron: str = <object object>)
    -

    ScheduleCron(cron: str = )

    +

    ScheduleCron(cron: str = )

    Expand source code @@ -984,7 +1249,7 @@

    Class variables

    (rate: str = <object object>)
    -

    ScheduleRate(rate: str = )

    +

    ScheduleRate(rate: str = )

    Expand source code @@ -1011,7 +1276,7 @@

    Class variables

    (key: str = <object object>, rate: ScheduleRate = <object object>, cron: ScheduleCron = <object object>)
    -

    ScheduleWorker(key: str = , rate: 'ScheduleRate' = , cron: 'ScheduleCron' = )

    +

    ScheduleWorker(key: str = , rate: 'ScheduleRate' = , cron: 'ScheduleCron' = )

    Expand source code @@ -1094,7 +1359,7 @@

    Class variables

    (topic: str = <object object>)
    -

    SubscriptionWorker(topic: str = )

    +

    SubscriptionWorker(topic: str = )

    Expand source code @@ -1155,7 +1420,7 @@

    Class variables

    (topic: str = <object object>)
    -

    TopicTriggerContext(topic: str = )

    +

    TopicTriggerContext(topic: str = )

    Expand source code @@ -1183,7 +1448,7 @@

    Class variables

    (values: Dict[str, str] = <object object>)
    -

    TraceContext(values: Dict[str, str] = )

    +

    TraceContext(values: Dict[str, str] = )

    Expand source code @@ -1209,7 +1474,7 @@

    Class variables

    class TriggerRequest -(data: bytes = <object object>, mime_type: str = <object object>, trace_context: TraceContext = <object object>, http: HttpTriggerContext = <object object>, topic: TopicTriggerContext = <object object>) +(data: bytes = <object object>, mime_type: str = <object object>, trace_context: TraceContext = <object object>, http: HttpTriggerContext = <object object>, topic: TopicTriggerContext = <object object>, notification: NotificationTriggerContext = <object object>)

    The server has a trigger for the client to handle

    @@ -1236,7 +1501,10 @@

    Class variables

    """ http: "HttpTriggerContext" = betterproto.message_field(3, group="context") - topic: "TopicTriggerContext" = betterproto.message_field(4, group="context") + topic: "TopicTriggerContext" = betterproto.message_field(4, group="context") + notification: "NotificationTriggerContext" = betterproto.message_field( + 5, group="context" + )

    Ancestors

      @@ -1257,6 +1525,10 @@

      Class variables

      Should we supply a mime type for the data? Or rely on context?

      +
      var notificationNotificationTriggerContext
      +
      +
      +
      var topicTopicTriggerContext
      @@ -1272,7 +1544,7 @@

      Class variables

      class TriggerResponse -(data: bytes = <object object>, http: HttpResponseContext = <object object>, topic: TopicResponseContext = <object object>) +(data: bytes = <object object>, http: HttpResponseContext = <object object>, topic: TopicResponseContext = <object object>, notification: NotificationResponseContext = <object object>)

      The worker has successfully processed a trigger

      @@ -1291,7 +1563,12 @@

      Class variables

      """response to a http request""" topic: "TopicResponseContext" = betterproto.message_field(11, group="context") - """response to a topic trigger""" + """response to a topic trigger""" + + notification: "NotificationResponseContext" = betterproto.message_field( + 12, group="context" + ) + """response to a notification trigger"""

      Ancestors

        @@ -1308,6 +1585,10 @@

        Class variables

        response to a http request

        +
        var notificationNotificationResponseContext
        +
        +

        response to a notification trigger

        +
        var topicTopicResponseContext

        response to a topic trigger

        @@ -1353,6 +1634,35 @@

        BucketNotification

        + + +
      • +

        BucketNotificationConfig

        + +
      • +
      • +

        BucketNotificationType

        + +
      • +
      • +

        BucketNotificationWorker

        + +
      • +
      • ClientMessage

        • id
        • @@ -1402,6 +1712,7 @@

          InitRequest

          @@ -1410,6 +1721,19 @@

          InitResponse

        • +

          NotificationResponseContext

          + +
        • +
        • +

          NotificationTriggerContext

          + +
        • +
        • QueryValue

          • value
          • @@ -1469,10 +1793,11 @@

            TriggerRequest

            - diff --git a/docs/nitric/proto/nitric/index.html b/docs/nitric/proto/nitric/index.html index 8c42e05..4e7f75d 100644 --- a/docs/nitric/proto/nitric/index.html +++ b/docs/nitric/proto/nitric/index.html @@ -19,9 +19,32 @@
            -

            Namespace nitric.proto.nitric

            +

            Module nitric.proto.nitric

            +
            + +Expand source code + +
            #
            +# Copyright (c) 2021 Nitric Technologies Pty Ltd.
            +#
            +# This file is part of Nitric Python 3 SDK.
            +# See https://github.com/nitrictech/python-sdk for further info.
            +#
            +# Licensed under the Apache License, Version 2.0 (the "License");
            +# you may not use this file except in compliance with the License.
            +# You may obtain a copy of the License at
            +#
            +#     http://www.apache.org/licenses/LICENSE-2.0
            +#
            +# Unless required by applicable law or agreed to in writing, software
            +# distributed under the License is distributed on an "AS IS" BASIS,
            +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
            +# See the License for the specific language governing permissions and
            +# limitations under the License.
            +#
            +

            Sub-modules

            diff --git a/docs/nitric/proto/nitric/queue/v1/index.html b/docs/nitric/proto/nitric/queue/v1/index.html index b6e1728..968d1e6 100644 --- a/docs/nitric/proto/nitric/queue/v1/index.html +++ b/docs/nitric/proto/nitric/queue/v1/index.html @@ -338,7 +338,7 @@

            Classes

            (task: NitricTask = <object object>, message: str = <object object>)
            -

            FailedTask(task: 'NitricTask' = , message: str = )

            +

            FailedTask(task: 'NitricTask' = , message: str = )

            Expand source code @@ -428,7 +428,7 @@

            Class variables

            (queue: str = <object object>, lease_id: str = <object object>)
            -

            QueueCompleteRequest(queue: str = , lease_id: str = )

            +

            QueueCompleteRequest(queue: str = , lease_id: str = )

            Expand source code @@ -487,7 +487,7 @@

            Ancestors

            (queue: str = <object object>, depth: int = <object object>)
            -

            QueueReceiveRequest(queue: str = , depth: int = )

            +

            QueueReceiveRequest(queue: str = , depth: int = )

            Expand source code @@ -530,7 +530,7 @@

            Class variables

            (tasks: List[ForwardRef('NitricTask')] = <object object>)
            -

            QueueReceiveResponse(tasks: List[ForwardRef('NitricTask')] = )

            +

            QueueReceiveResponse(tasks: List[ForwardRef('NitricTask')] = )

            Expand source code @@ -558,7 +558,7 @@

            Class variables

            (queue: str = <object object>, tasks: List[ForwardRef('NitricTask')] = <object object>)
            -

            QueueSendBatchRequest(queue: str = , tasks: List[ForwardRef('NitricTask')] = )

            +

            QueueSendBatchRequest(queue: str = , tasks: List[ForwardRef('NitricTask')] = )

            Expand source code diff --git a/docs/nitric/proto/nitric/resource/v1/index.html b/docs/nitric/proto/nitric/resource/v1/index.html index cb3c949..0837766 100644 --- a/docs/nitric/proto/nitric/resource/v1/index.html +++ b/docs/nitric/proto/nitric/resource/v1/index.html @@ -78,6 +78,7 @@

            Module nitric.proto.nitric.resource.v1

            Collection = 7 Policy = 8 Secret = 9 + Notification = 10 class Action(betterproto.Enum): @@ -441,7 +442,7 @@

            Class variables

            (security_definitions: Dict[str, ForwardRef('ApiSecurityDefinition')] = <object object>, security: Dict[str, ForwardRef('ApiScopes')] = <object object>)
            -

            ApiResource(security_definitions: Dict[str, ForwardRef('ApiSecurityDefinition')] = , security: Dict[str, ForwardRef('ApiScopes')] = )

            +

            ApiResource(security_definitions: Dict[str, ForwardRef('ApiSecurityDefinition')] = , security: Dict[str, ForwardRef('ApiScopes')] = )

            Expand source code @@ -484,7 +485,7 @@

            Class variables

            (url: str = <object object>)
            -

            ApiResourceDetails(url: str = )

            +

            ApiResourceDetails(url: str = )

            Expand source code @@ -511,7 +512,7 @@

            Class variables

            (scopes: List[str] = <object object>)
            -

            ApiScopes(scopes: List[str] = )

            +

            ApiScopes(scopes: List[str] = )

            Expand source code @@ -538,7 +539,7 @@

            Class variables

            (jwt: ApiSecurityDefinitionJwt = <object object>)
            -

            ApiSecurityDefinition(jwt: 'ApiSecurityDefinitionJwt' = )

            +

            ApiSecurityDefinition(jwt: 'ApiSecurityDefinitionJwt' = )

            Expand source code @@ -637,7 +638,7 @@

            Ancestors

            (principals: List[ForwardRef('Resource')] = <object object>, actions: List[ForwardRef('Action')] = <object object>, resources: List[ForwardRef('Resource')] = <object object>)
            -

            PolicyResource(principals: List[ForwardRef('Resource')] = , actions: List[ForwardRef('Action')] = , resources: List[ForwardRef('Resource')] = )

            +

            PolicyResource(principals: List[ForwardRef('Resource')] = , actions: List[ForwardRef('Action')] = , resources: List[ForwardRef('Resource')] = )

            Expand source code @@ -693,7 +694,7 @@

            Ancestors

            (type: ResourceType = <object object>, name: str = <object object>)
            -

            Resource(type: 'ResourceType' = , name: str = )

            +

            Resource(type: 'ResourceType' = , name: str = )

            Expand source code @@ -725,7 +726,7 @@

            Class variables

            (resource: Resource = <object object>, policy: PolicyResource = <object object>, bucket: BucketResource = <object object>, queue: QueueResource = <object object>, topic: TopicResource = <object object>, collection: CollectionResource = <object object>, secret: SecretResource = <object object>, api: ApiResource = <object object>)
            -

            ResourceDeclareRequest(resource: 'Resource' = , policy: 'PolicyResource' = , bucket: 'BucketResource' = , queue: 'QueueResource' = , topic: 'TopicResource' = , collection: 'CollectionResource' = , secret: 'SecretResource' = , api: 'ApiResource' = )

            +

            ResourceDeclareRequest(resource: 'Resource' = , policy: 'PolicyResource' = , bucket: 'BucketResource' = , queue: 'QueueResource' = , topic: 'TopicResource' = , collection: 'CollectionResource' = , secret: 'SecretResource' = , api: 'ApiResource' = )

            Expand source code @@ -806,7 +807,7 @@

            Ancestors

            (resource: Resource = <object object>)
            -

            ResourceDetailsRequest(resource: 'Resource' = )

            +

            ResourceDetailsRequest(resource: 'Resource' = )

            Expand source code @@ -833,7 +834,7 @@

            Class variables

            (id: str = <object object>, provider: str = <object object>, service: str = <object object>, api: ApiResourceDetails = <object object>)
            -

            ResourceDetailsResponse(id: str = , provider: str = , service: str = , api: 'ApiResourceDetails' = )

            +

            ResourceDetailsResponse(id: str = , provider: str = , service: str = , api: 'ApiResourceDetails' = )

            Expand source code @@ -1095,7 +1096,8 @@

            Methods

            Subscription = 6 Collection = 7 Policy = 8 - Secret = 9 + Secret = 9 + Notification = 10

            Ancestors

              @@ -1122,6 +1124,10 @@

              Class variables

              +
              var Notification
              +
              +
              +
              var Policy
              @@ -1333,6 +1339,7 @@

              Bucket
            • Collection
            • Function
            • +
            • Notification
            • Policy
            • Queue
            • Schedule
            • diff --git a/docs/nitric/proto/nitric/storage/v1/index.html b/docs/nitric/proto/nitric/storage/v1/index.html index 14bf58c..fca7e88 100644 --- a/docs/nitric/proto/nitric/storage/v1/index.html +++ b/docs/nitric/proto/nitric/storage/v1/index.html @@ -391,7 +391,7 @@

              Classes

              (key: str = <object object>)
              -

              File(key: str = )

              +

              File(key: str = )

              Expand source code @@ -476,7 +476,7 @@

              Ancestors

              (bucket_name: str = <object object>)
              -

              StorageListFilesRequest(bucket_name: str = )

              +

              StorageListFilesRequest(bucket_name: str = )

              Expand source code @@ -503,7 +503,7 @@

              Class variables

              (files: List[ForwardRef('File')] = <object object>)
              -

              StorageListFilesResponse(files: List[ForwardRef('File')] = )

              +

              StorageListFilesResponse(files: List[ForwardRef('File')] = )

              Expand source code @@ -636,7 +636,7 @@

              Class variables

              (url: str = <object object>)
              -

              StoragePreSignUrlResponse(url: str = )

              +

              StoragePreSignUrlResponse(url: str = )

              Expand source code diff --git a/docs/nitric/resources/apis.html b/docs/nitric/resources/apis.html index 5209d3b..2a59135 100644 --- a/docs/nitric/resources/apis.html +++ b/docs/nitric/resources/apis.html @@ -61,7 +61,7 @@

              Module nitric.resources.apis

              ResourceDetailsRequest, ) from grpclib import GRPCError -from nitric.api.exception import exception_from_grpc_error +from nitric.exception import exception_from_grpc_error @dataclass @@ -80,7 +80,12 @@

              Module nitric.resources.apis

              @dataclass class JwtSecurityDefinition: - """Represents the JWT security definition for an API.""" + """ + Represents the JWT security definition for an API. + + issuer (str): the JWT issuer + audiences (List[str]): a list of the allowed audiences for the API + """ issuer: str audiences: List[str] @@ -96,16 +101,16 @@

              Module nitric.resources.apis

              """Represents options when creating an API, such as middleware to be applied to all HTTP request to the API.""" path: str - middleware: Union[HttpMiddleware, List[HttpMiddleware], None] - security_definitions: Union[dict[str, SecurityDefinition], None] - security: Union[dict[str, List[str]], None] + middleware: Union[HttpMiddleware, List[HttpMiddleware]] + security_definitions: dict[str, SecurityDefinition] + security: dict[str, List[str]] def __init__( self, path: str = "", middleware: List[Middleware] = [], - security_definitions: dict[str, SecurityDefinition] = None, - security: dict[str, List[str]] = None, + security_definitions: dict[str, SecurityDefinition] = {}, + security: dict[str, List[str]] = {}, ): """Construct a new API options object.""" self.middleware = middleware @@ -130,18 +135,18 @@

              Module nitric.resources.apis

              def _security_definition_to_grpc_declaration( security_definitions: dict[str, SecurityDefinition] -) -> Union[dict[str, ApiSecurityDefinition], None]: +) -> dict[str, ApiSecurityDefinition]: if security_definitions is None or len(security_definitions) == 0: - return None + return {} return { k: ApiSecurityDefinition(jwt=ApiSecurityDefinitionJwt(issuer=v.issuer, audiences=v.audiences)) for k, v in security_definitions.items() } -def _security_to_grpc_declaration(security: dict[str, List[str]]) -> dict[str, ApiScopes] | None: +def _security_to_grpc_declaration(security: dict[str, List[str]]) -> dict[str, ApiScopes]: if security is None or len(security) == 0: - return None + return {} return {k: ApiScopes(v) for k, v in security.items()} @@ -215,7 +220,7 @@

              Module nitric.resources.apis

              return decorator def methods(self, methods: List[HttpMethod], match: str, opts: MethodOptions = None): - """Define an HTTP route which will respond to HTTP GET requests.""" + """Define an HTTP route which will respond to specific HTTP requests defined by a list of verbs.""" if opts is None: opts = MethodOptions() @@ -303,7 +308,7 @@

              Module nitric.resources.apis

              except GRPCError as grpc_err: raise exception_from_grpc_error(grpc_err) - async def URL(self) -> str: + async def url(self) -> str: """Get the APIs live URL.""" details = await self._details() return details.url @@ -319,7 +324,7 @@

              Module nitric.resources.apis

              def __init__(self, api: Api, path: str, opts: RouteOptions): """Define a route to be handled by the provided API.""" self.api = api - self.path = api.path.join(path) + self.path = (api.path + path).replace("//", "/") self.middleware = opts.middleware if opts.middleware is not None else [] def method(self, methods: List[HttpMethod], *middleware: HttpMiddleware, opts: MethodOptions = None): @@ -485,7 +490,7 @@

              Classes

              return decorator def methods(self, methods: List[HttpMethod], match: str, opts: MethodOptions = None): - """Define an HTTP route which will respond to HTTP GET requests.""" + """Define an HTTP route which will respond to specific HTTP requests defined by a list of verbs.""" if opts is None: opts = MethodOptions() @@ -573,7 +578,7 @@

              Classes

              except GRPCError as grpc_err: raise exception_from_grpc_error(grpc_err) - async def URL(self) -> str: + async def url(self) -> str: """Get the APIs live URL.""" details = await self._details() return details.url @@ -616,21 +621,6 @@

              Class variables

              Methods

              -
              -async def URL(self) ‑> str -
              -
              -

              Get the APIs live URL.

              -
              - -Expand source code - -
              async def URL(self) -> str:
              -    """Get the APIs live URL."""
              -    details = await self._details()
              -    return details.url
              -
              -
              def all(self, match: str, opts: MethodOptions = None)
              @@ -709,13 +699,13 @@

              Methods

              def methods(self, methods: List[HttpMethod], match: str, opts: MethodOptions = None)
              -

              Define an HTTP route which will respond to HTTP GET requests.

              +

              Define an HTTP route which will respond to specific HTTP requests defined by a list of verbs.

              Expand source code
              def methods(self, methods: List[HttpMethod], match: str, opts: MethodOptions = None):
              -    """Define an HTTP route which will respond to HTTP GET requests."""
              +    """Define an HTTP route which will respond to specific HTTP requests defined by a list of verbs."""
                   if opts is None:
                       opts = MethodOptions()
               
              @@ -810,6 +800,21 @@ 

              Methods

              return decorator
              +
              +async def url(self) ‑> str +
              +
              +

              Get the APIs live URL.

              +
              + +Expand source code + +
              async def url(self) -> str:
              +    """Get the APIs live URL."""
              +    details = await self._details()
              +    return details.url
              +
              +

              Inherited members

                @@ -865,7 +870,7 @@

                Class variables

                class ApiOptions -(path: str = '', middleware: List[Middleware] = [], security_definitions: dict[str, JwtSecurityDefinition] = None, security: dict[str, List[str]] = None) +(path: str = '', middleware: List[Middleware] = [], security_definitions: dict[str, JwtSecurityDefinition] = {}, security: dict[str, List[str]] = {})

                Represents options when creating an API, such as middleware to be applied to all HTTP request to the API.

                @@ -878,16 +883,16 @@

                Class variables

                """Represents options when creating an API, such as middleware to be applied to all HTTP request to the API.""" path: str - middleware: Union[HttpMiddleware, List[HttpMiddleware], None] - security_definitions: Union[dict[str, SecurityDefinition], None] - security: Union[dict[str, List[str]], None] + middleware: Union[HttpMiddleware, List[HttpMiddleware]] + security_definitions: dict[str, SecurityDefinition] + security: dict[str, List[str]] def __init__( self, path: str = "", middleware: List[Middleware] = [], - security_definitions: dict[str, SecurityDefinition] = None, - security: dict[str, List[str]] = None, + security_definitions: dict[str, SecurityDefinition] = {}, + security: dict[str, List[str]] = {}, ): """Construct a new API options object.""" self.middleware = middleware @@ -897,7 +902,7 @@

                Class variables

              Class variables

              -
              var middleware : Union[Callable[[HttpContext, Coroutine[Any, Any, Optional[HttpContext]]], Coroutine[Any, Any, Optional[HttpContext]]], List[Callable[[HttpContext, Coroutine[Any, Any, Optional[HttpContext]]], Coroutine[Any, Any, Optional[HttpContext]]]], NoneType]
              +
              var middleware : Union[Callable[[HttpContext, Coroutine[Any, Any, Optional[HttpContext]]], Coroutine[Any, Any, Optional[HttpContext]]], List[Callable[[HttpContext, Coroutine[Any, Any, Optional[HttpContext]]], Coroutine[Any, Any, Optional[HttpContext]]]]]
              @@ -905,11 +910,11 @@

              Class variables

              -
              var security : Optional[dict[str, List[str]]]
              +
              var security : dict[str, typing.List[str]]
              -
              var security_definitions : Optional[dict[str, JwtSecurityDefinition]]
              +
              var security_definitions : dict[str, JwtSecurityDefinition]
              @@ -920,14 +925,21 @@

              Class variables

              (issuer: str, audiences: List[str])
              -

              Represents the JWT security definition for an API.

              +

              Represents the JWT security definition for an API.

              +

              issuer (str): the JWT issuer +audiences (List[str]): a list of the allowed audiences for the API

              Expand source code
              @dataclass
               class JwtSecurityDefinition:
              -    """Represents the JWT security definition for an API."""
              +    """
              +    Represents the JWT security definition for an API.
              +
              +    issuer (str): the JWT issuer
              +    audiences (List[str]): a list of the allowed audiences for the API
              +    """
               
                   issuer: str
                   audiences: List[str]
              @@ -949,14 +961,21 @@

              Class variables

              (issuer: str, audiences: List[str])
              -

              Represents the JWT security definition for an API.

              +

              Represents the JWT security definition for an API.

              +

              issuer (str): the JWT issuer +audiences (List[str]): a list of the allowed audiences for the API

              Expand source code
              @dataclass
               class JwtSecurityDefinition:
              -    """Represents the JWT security definition for an API."""
              +    """
              +    Represents the JWT security definition for an API.
              +
              +    issuer (str): the JWT issuer
              +    audiences (List[str]): a list of the allowed audiences for the API
              +    """
               
                   issuer: str
                   audiences: List[str]
              @@ -1063,7 +1082,7 @@

              Methods

              def __init__(self, api: Api, path: str, opts: RouteOptions): """Define a route to be handled by the provided API.""" self.api = api - self.path = api.path.join(path) + self.path = (api.path + path).replace("//", "/") self.middleware = opts.middleware if opts.middleware is not None else [] def method(self, methods: List[HttpMethod], *middleware: HttpMiddleware, opts: MethodOptions = None): @@ -1263,7 +1282,6 @@

              Index

            • Api

            • diff --git a/docs/nitric/resources/base.html b/docs/nitric/resources/base.html index a82c334..d395060 100644 --- a/docs/nitric/resources/base.html +++ b/docs/nitric/resources/base.html @@ -62,7 +62,7 @@

              Module nitric.resources.base

              ResourceServiceStub, ) -from nitric.api.exception import exception_from_grpc_error, NitricResourceException +from nitric.exception import exception_from_grpc_error, NitricResourceException from nitric.utils import new_default_channel T = TypeVar("T", bound="BaseResource") @@ -71,8 +71,6 @@

              Module nitric.resources.base

              class BaseResource(ABC): """A base resource class with common functionality.""" - cache = {} - def __init__(self): """Construct a new resource.""" self._reg: Union[Task, None] = None @@ -90,7 +88,6 @@

              Module nitric.resources.base

              The registration process for resources async, so this method should be used instead of __init__. """ - # Todo: store the resource reference in a cache to avoid duplicate registrations r = cls(name, *args, **kwargs) try: loop = asyncio.get_running_loop() @@ -165,8 +162,6 @@

              Classes

              class BaseResource(ABC):
                   """A base resource class with common functionality."""
               
              -    cache = {}
              -
                   def __init__(self):
                       """Construct a new resource."""
                       self._reg: Union[Task, None] = None
              @@ -184,7 +179,6 @@ 

              Classes

              The registration process for resources async, so this method should be used instead of __init__. """ - # Todo: store the resource reference in a cache to avoid duplicate registrations r = cls(name, *args, **kwargs) try: loop = asyncio.get_running_loop() @@ -204,13 +198,6 @@

              Subclasses

            • Api
            • SecureResource
            • -

              Class variables

              -
              -
              var cache
              -
              -
              -
              -

              Static methods

              @@ -230,7 +217,6 @@

              Static methods

              The registration process for resources async, so this method should be used instead of __init__. """ - # Todo: store the resource reference in a cache to avoid duplicate registrations r = cls(name, *args, **kwargs) try: loop = asyncio.get_running_loop() @@ -335,7 +321,6 @@

              Index

            • BaseResource

            • diff --git a/docs/nitric/resources/buckets.html b/docs/nitric/resources/buckets.html index 9c7eb41..e399f25 100644 --- a/docs/nitric/resources/buckets.html +++ b/docs/nitric/resources/buckets.html @@ -46,13 +46,14 @@

              Module nitric.resources.buckets

              # from __future__ import annotations -from nitric.api.exception import exception_from_grpc_error +from nitric.exception import exception_from_grpc_error from nitric.api.storage import BucketRef, Storage from typing import List, Union from enum import Enum from grpclib import GRPCError from nitric.application import Nitric +from nitric.faas import FunctionServer, BucketNotificationWorkerOptions, BucketNotificationMiddleware from nitric.proto.nitric.resource.v1 import ( Resource, ResourceType, @@ -76,6 +77,7 @@

              Module nitric.resources.buckets

              name: str actions: List[Action] + _server: FunctionServer def __init__(self, name: str): """Create a bucket with the name provided or references it if it already exists.""" @@ -90,7 +92,7 @@

              Module nitric.resources.buckets

              except GRPCError as grpc_err: raise exception_from_grpc_error(grpc_err) - def _perms_to_actions(self, *args: [Union[BucketPermission, str]]) -> List[Action]: + def _perms_to_actions(self, *args: List[Union[BucketPermission, str]]) -> List[Action]: permission_actions_map = { BucketPermission.reading: [Action.BucketFileGet, Action.BucketFileList], BucketPermission.writing: [Action.BucketFilePut], @@ -112,6 +114,24 @@

              Module nitric.resources.buckets

              return Storage().bucket(self.name) + def on(self, notification_type: str, notification_prefix_filter: str): + """Create and return a bucket notification decorator for this bucket.""" + print("this has been called") + + def decorator(func: BucketNotificationMiddleware): + print("this has been called") + self._server = FunctionServer( + BucketNotificationWorkerOptions( + bucket_name=self.name, + notification_type=notification_type, + notification_prefix_filter=notification_prefix_filter, + ) + ) + self._server.bucket_notification(func) + return Nitric._register_worker(self._server) + + return decorator + def bucket(name: str) -> Bucket: """ @@ -169,6 +189,7 @@

              Classes

              name: str actions: List[Action] + _server: FunctionServer def __init__(self, name: str): """Create a bucket with the name provided or references it if it already exists.""" @@ -183,7 +204,7 @@

              Classes

              except GRPCError as grpc_err: raise exception_from_grpc_error(grpc_err) - def _perms_to_actions(self, *args: [Union[BucketPermission, str]]) -> List[Action]: + def _perms_to_actions(self, *args: List[Union[BucketPermission, str]]) -> List[Action]: permission_actions_map = { BucketPermission.reading: [Action.BucketFileGet, Action.BucketFileList], BucketPermission.writing: [Action.BucketFilePut], @@ -203,7 +224,25 @@

              Classes

              """Request the required permissions for this resource.""" self._register_policy(*args) - return Storage().bucket(self.name)
              + return Storage().bucket(self.name) + + def on(self, notification_type: str, notification_prefix_filter: str): + """Create and return a bucket notification decorator for this bucket.""" + print("this has been called") + + def decorator(func: BucketNotificationMiddleware): + print("this has been called") + self._server = FunctionServer( + BucketNotificationWorkerOptions( + bucket_name=self.name, + notification_type=notification_type, + notification_prefix_filter=notification_prefix_filter, + ) + ) + self._server.bucket_notification(func) + return Nitric._register_worker(self._server) + + return decorator
            • Ancestors

                @@ -240,6 +279,34 @@

                Methods

                return Storage().bucket(self.name)
              +
              +def on(self, notification_type: str, notification_prefix_filter: str) +
              +
              +

              Create and return a bucket notification decorator for this bucket.

              +
              + +Expand source code + +
              def on(self, notification_type: str, notification_prefix_filter: str):
              +    """Create and return a bucket notification decorator for this bucket."""
              +    print("this has been called")
              +
              +    def decorator(func: BucketNotificationMiddleware):
              +        print("this has been called")
              +        self._server = FunctionServer(
              +            BucketNotificationWorkerOptions(
              +                bucket_name=self.name,
              +                notification_type=notification_type,
              +                notification_prefix_filter=notification_prefix_filter,
              +            )
              +        )
              +        self._server.bucket_notification(func)
              +        return Nitric._register_worker(self._server)
              +
              +    return decorator
              +
              +

              Inherited members

            • diff --git a/docs/nitric/resources/collections.html b/docs/nitric/resources/collections.html index 1ae62ae..3cf1178 100644 --- a/docs/nitric/resources/collections.html +++ b/docs/nitric/resources/collections.html @@ -47,7 +47,7 @@

              Module nitric.resources.collections

              from __future__ import annotations from nitric.api.documents import CollectionRef, Documents -from nitric.api.exception import exception_from_grpc_error +from nitric.exception import exception_from_grpc_error from typing import List, Union from enum import Enum from grpclib import GRPCError diff --git a/docs/nitric/resources/index.html b/docs/nitric/resources/index.html index b9bd510..6ddbe7b 100644 --- a/docs/nitric/resources/index.html +++ b/docs/nitric/resources/index.html @@ -334,7 +334,7 @@

              Classes

              return decorator def methods(self, methods: List[HttpMethod], match: str, opts: MethodOptions = None): - """Define an HTTP route which will respond to HTTP GET requests.""" + """Define an HTTP route which will respond to specific HTTP requests defined by a list of verbs.""" if opts is None: opts = MethodOptions() @@ -422,7 +422,7 @@

              Classes

              except GRPCError as grpc_err: raise exception_from_grpc_error(grpc_err) - async def URL(self) -> str: + async def url(self) -> str: """Get the APIs live URL.""" details = await self._details() return details.url @@ -465,21 +465,6 @@

              Class variables

              Methods

              -
              -async def URL(self) ‑> str -
              -
              -

              Get the APIs live URL.

              -
              - -Expand source code - -
              async def URL(self) -> str:
              -    """Get the APIs live URL."""
              -    details = await self._details()
              -    return details.url
              -
              -
              def all(self, match: str, opts: MethodOptions = None)
              @@ -558,13 +543,13 @@

              Methods

              def methods(self, methods: List[HttpMethod], match: str, opts: MethodOptions = None)
              -

              Define an HTTP route which will respond to HTTP GET requests.

              +

              Define an HTTP route which will respond to specific HTTP requests defined by a list of verbs.

              Expand source code
              def methods(self, methods: List[HttpMethod], match: str, opts: MethodOptions = None):
              -    """Define an HTTP route which will respond to HTTP GET requests."""
              +    """Define an HTTP route which will respond to specific HTTP requests defined by a list of verbs."""
                   if opts is None:
                       opts = MethodOptions()
               
              @@ -659,6 +644,21 @@ 

              Methods

              return decorator
              +
              +async def url(self) ‑> str +
              +
              +

              Get the APIs live URL.

              +
              + +Expand source code + +
              async def url(self) -> str:
              +    """Get the APIs live URL."""
              +    details = await self._details()
              +    return details.url
              +
              +

              Inherited members

                @@ -714,7 +714,7 @@

                Class variables

                class ApiOptions -(path: str = '', middleware: List[Middleware] = [], security_definitions: dict[str, SecurityDefinition] = None, security: dict[str, List[str]] = None) +(path: str = '', middleware: List[Middleware] = [], security_definitions: dict[str, SecurityDefinition] = {}, security: dict[str, List[str]] = {})

                Represents options when creating an API, such as middleware to be applied to all HTTP request to the API.

                @@ -727,16 +727,16 @@

                Class variables

                """Represents options when creating an API, such as middleware to be applied to all HTTP request to the API.""" path: str - middleware: Union[HttpMiddleware, List[HttpMiddleware], None] - security_definitions: Union[dict[str, SecurityDefinition], None] - security: Union[dict[str, List[str]], None] + middleware: Union[HttpMiddleware, List[HttpMiddleware]] + security_definitions: dict[str, SecurityDefinition] + security: dict[str, List[str]] def __init__( self, path: str = "", middleware: List[Middleware] = [], - security_definitions: dict[str, SecurityDefinition] = None, - security: dict[str, List[str]] = None, + security_definitions: dict[str, SecurityDefinition] = {}, + security: dict[str, List[str]] = {}, ): """Construct a new API options object.""" self.middleware = middleware @@ -746,7 +746,7 @@

                Class variables

                Class variables

                -
                var middleware : Union[Callable[[HttpContext, Coroutine[Any, Any, Optional[HttpContext]]], Coroutine[Any, Any, Optional[HttpContext]]], List[Callable[[HttpContext, Coroutine[Any, Any, Optional[HttpContext]]], Coroutine[Any, Any, Optional[HttpContext]]]], NoneType]
                +
                var middleware : Union[Callable[[HttpContext, Coroutine[Any, Any, Optional[HttpContext]]], Coroutine[Any, Any, Optional[HttpContext]]], List[Callable[[HttpContext, Coroutine[Any, Any, Optional[HttpContext]]], Coroutine[Any, Any, Optional[HttpContext]]]]]
                @@ -754,11 +754,11 @@

                Class variables

                -
                var security : Optional[dict[str, List[str]]]
                +
                var security : dict[str, typing.List[str]]
                -
                var security_definitions : Optional[dict[str, JwtSecurityDefinition]]
                +
                var security_definitions : dict[str, JwtSecurityDefinition]
                @@ -780,6 +780,7 @@

                Class variables

                name: str actions: List[Action] + _server: FunctionServer def __init__(self, name: str): """Create a bucket with the name provided or references it if it already exists.""" @@ -794,7 +795,7 @@

                Class variables

                except GRPCError as grpc_err: raise exception_from_grpc_error(grpc_err) - def _perms_to_actions(self, *args: [Union[BucketPermission, str]]) -> List[Action]: + def _perms_to_actions(self, *args: List[Union[BucketPermission, str]]) -> List[Action]: permission_actions_map = { BucketPermission.reading: [Action.BucketFileGet, Action.BucketFileList], BucketPermission.writing: [Action.BucketFilePut], @@ -814,7 +815,25 @@

                Class variables

                """Request the required permissions for this resource.""" self._register_policy(*args) - return Storage().bucket(self.name) + return Storage().bucket(self.name) + + def on(self, notification_type: str, notification_prefix_filter: str): + """Create and return a bucket notification decorator for this bucket.""" + print("this has been called") + + def decorator(func: BucketNotificationMiddleware): + print("this has been called") + self._server = FunctionServer( + BucketNotificationWorkerOptions( + bucket_name=self.name, + notification_type=notification_type, + notification_prefix_filter=notification_prefix_filter, + ) + ) + self._server.bucket_notification(func) + return Nitric._register_worker(self._server) + + return decorator

                Ancestors

                  @@ -851,6 +870,34 @@

                  Methods

                  return Storage().bucket(self.name)
                +
                +def on(self, notification_type: str, notification_prefix_filter: str) +
                +
                +

                Create and return a bucket notification decorator for this bucket.

                +
                + +Expand source code + +
                def on(self, notification_type: str, notification_prefix_filter: str):
                +    """Create and return a bucket notification decorator for this bucket."""
                +    print("this has been called")
                +
                +    def decorator(func: BucketNotificationMiddleware):
                +        print("this has been called")
                +        self._server = FunctionServer(
                +            BucketNotificationWorkerOptions(
                +                bucket_name=self.name,
                +                notification_type=notification_type,
                +                notification_prefix_filter=notification_prefix_filter,
                +            )
                +        )
                +        self._server.bucket_notification(func)
                +        return Nitric._register_worker(self._server)
                +
                +    return decorator
                +
                +

                Inherited members

                  @@ -956,14 +1003,21 @@

                  Inherited members

                  (issuer: str, audiences: List[str])
                  -

                  Represents the JWT security definition for an API.

                  +

                  Represents the JWT security definition for an API.

                  +

                  issuer (str): the JWT issuer +audiences (List[str]): a list of the allowed audiences for the API

                  Expand source code
                  @dataclass
                   class JwtSecurityDefinition:
                  -    """Represents the JWT security definition for an API."""
                  +    """
                  +    Represents the JWT security definition for an API.
                  +
                  +    issuer (str): the JWT issuer
                  +    audiences (List[str]): a list of the allowed audiences for the API
                  +    """
                   
                       issuer: str
                       audiences: List[str]
                  @@ -1144,17 +1198,15 @@

                  Inherited members

                  # handle singular frequencies. e.g. every('day') rate_description = f"1 {rate_description}s" # 'day' becomes '1 days' - rate, freq_str = rate_description.split(" ") - freq = Frequency.from_str(freq_str) + try: + rate, freq_str = rate_description.split(" ") + freq = Frequency.from_str(freq_str) + except Exception: + raise Exception(f"invalid rate expression, frequency must be one of {Frequency.as_str_list()}") if not rate.isdigit(): raise Exception("invalid rate expression, expression must begin with a positive integer") - if not freq: - raise Exception( - f"invalid rate expression, frequency must be one of ${Frequency.as_str_list()}, received ${freq_str}" - ) - opts = RateWorkerOptions(self.description, int(rate), freq) self.server = FunctionServer(opts) @@ -1196,17 +1248,15 @@

                  Methods

                  # handle singular frequencies. e.g. every('day') rate_description = f"1 {rate_description}s" # 'day' becomes '1 days' - rate, freq_str = rate_description.split(" ") - freq = Frequency.from_str(freq_str) + try: + rate, freq_str = rate_description.split(" ") + freq = Frequency.from_str(freq_str) + except Exception: + raise Exception(f"invalid rate expression, frequency must be one of {Frequency.as_str_list()}") if not rate.isdigit(): raise Exception("invalid rate expression, expression must begin with a positive integer") - if not freq: - raise Exception( - f"invalid rate expression, frequency must be one of ${Frequency.as_str_list()}, received ${freq_str}" - ) - opts = RateWorkerOptions(self.description, int(rate), freq) self.server = FunctionServer(opts) @@ -1343,7 +1393,6 @@

                  Inherited members

                  name: str actions: List[Action] - server: FunctionServer def __init__(self, name: str): """Construct a new topic.""" @@ -1377,11 +1426,15 @@

                  Inherited members

                  return Events().topic(self.name) - def subscribe(self, func: EventMiddleware): + def subscribe(self): """Create and return a subscription decorator for this topic.""" - self.server = FunctionServer(SubscriptionWorkerOptions(topic=self.name)) - self.server.event(func) - Nitric._register_worker(self.server) + + def decorator(func: EventMiddleware): + self.server = FunctionServer(SubscriptionWorkerOptions(topic=self.name)) + self.server.event(func) + Nitric._register_worker(self.server) + + return decorator

                  Ancestors

                    @@ -1399,10 +1452,6 @@

                    Class variables

                    -
                    var serverFunctionServer
                    -
                    -
                    -

                    Methods

                    @@ -1423,7 +1472,7 @@

                    Methods

                  -def subscribe(self, func: EventMiddleware) +def subscribe(self)

                  Create and return a subscription decorator for this topic.

                  @@ -1431,11 +1480,15 @@

                  Methods

                  Expand source code -
                  def subscribe(self, func: EventMiddleware):
                  +
                  def subscribe(self):
                       """Create and return a subscription decorator for this topic."""
                  -    self.server = FunctionServer(SubscriptionWorkerOptions(topic=self.name))
                  -    self.server.event(func)
                  -    Nitric._register_worker(self.server)
                  + + def decorator(func: EventMiddleware): + self.server = FunctionServer(SubscriptionWorkerOptions(topic=self.name)) + self.server.event(func) + Nitric._register_worker(self.server) + + return decorator
                  @@ -1490,7 +1543,6 @@

                  Index

                • Api

                • @@ -1532,6 +1585,7 @@

                  Buc
                • actions
                • allow
                • name
                • +
                • on
              • @@ -1584,7 +1638,6 @@

                Topic
              • actions
              • allow
              • name
              • -
              • server
              • subscribe
            • diff --git a/docs/nitric/resources/queues.html b/docs/nitric/resources/queues.html index 46d7c00..56b68cd 100644 --- a/docs/nitric/resources/queues.html +++ b/docs/nitric/resources/queues.html @@ -46,7 +46,7 @@

              Module nitric.resources.queues

              # from __future__ import annotations -from nitric.api.exception import exception_from_grpc_error +from nitric.exception import exception_from_grpc_error from typing import List, Union from enum import Enum from grpclib import GRPCError diff --git a/docs/nitric/resources/schedules.html b/docs/nitric/resources/schedules.html index 6337e09..1d5857e 100644 --- a/docs/nitric/resources/schedules.html +++ b/docs/nitric/resources/schedules.html @@ -75,17 +75,15 @@

              Module nitric.resources.schedules

              # handle singular frequencies. e.g. every('day') rate_description = f"1 {rate_description}s" # 'day' becomes '1 days' - rate, freq_str = rate_description.split(" ") - freq = Frequency.from_str(freq_str) + try: + rate, freq_str = rate_description.split(" ") + freq = Frequency.from_str(freq_str) + except Exception: + raise Exception(f"invalid rate expression, frequency must be one of {Frequency.as_str_list()}") if not rate.isdigit(): raise Exception("invalid rate expression, expression must begin with a positive integer") - if not freq: - raise Exception( - f"invalid rate expression, frequency must be one of ${Frequency.as_str_list()}, received ${freq_str}" - ) - opts = RateWorkerOptions(self.description, int(rate), freq) self.server = FunctionServer(opts) @@ -173,17 +171,15 @@

              Classes

              # handle singular frequencies. e.g. every('day') rate_description = f"1 {rate_description}s" # 'day' becomes '1 days' - rate, freq_str = rate_description.split(" ") - freq = Frequency.from_str(freq_str) + try: + rate, freq_str = rate_description.split(" ") + freq = Frequency.from_str(freq_str) + except Exception: + raise Exception(f"invalid rate expression, frequency must be one of {Frequency.as_str_list()}") if not rate.isdigit(): raise Exception("invalid rate expression, expression must begin with a positive integer") - if not freq: - raise Exception( - f"invalid rate expression, frequency must be one of ${Frequency.as_str_list()}, received ${freq_str}" - ) - opts = RateWorkerOptions(self.description, int(rate), freq) self.server = FunctionServer(opts) @@ -225,17 +221,15 @@

              Methods

              # handle singular frequencies. e.g. every('day') rate_description = f"1 {rate_description}s" # 'day' becomes '1 days' - rate, freq_str = rate_description.split(" ") - freq = Frequency.from_str(freq_str) + try: + rate, freq_str = rate_description.split(" ") + freq = Frequency.from_str(freq_str) + except Exception: + raise Exception(f"invalid rate expression, frequency must be one of {Frequency.as_str_list()}") if not rate.isdigit(): raise Exception("invalid rate expression, expression must begin with a positive integer") - if not freq: - raise Exception( - f"invalid rate expression, frequency must be one of ${Frequency.as_str_list()}, received ${freq_str}" - ) - opts = RateWorkerOptions(self.description, int(rate), freq) self.server = FunctionServer(opts) diff --git a/docs/nitric/resources/secrets.html b/docs/nitric/resources/secrets.html index 998fb5a..3ef3f62 100644 --- a/docs/nitric/resources/secrets.html +++ b/docs/nitric/resources/secrets.html @@ -46,7 +46,7 @@

              Module nitric.resources.secrets

              # from __future__ import annotations -from nitric.api.exception import exception_from_grpc_error +from nitric.exception import exception_from_grpc_error from typing import List, Union from enum import Enum from grpclib import GRPCError diff --git a/docs/nitric/resources/topics.html b/docs/nitric/resources/topics.html index 05fb81a..0a72d10 100644 --- a/docs/nitric/resources/topics.html +++ b/docs/nitric/resources/topics.html @@ -47,7 +47,7 @@

              Module nitric.resources.topics

              from __future__ import annotations from nitric.api.events import Events, TopicRef -from nitric.api.exception import exception_from_grpc_error +from nitric.exception import exception_from_grpc_error from typing import List, Union from enum import Enum from grpclib import GRPCError @@ -74,7 +74,6 @@

              Module nitric.resources.topics

              name: str actions: List[Action] - server: FunctionServer def __init__(self, name: str): """Construct a new topic.""" @@ -108,11 +107,15 @@

              Module nitric.resources.topics

              return Events().topic(self.name) - def subscribe(self, func: EventMiddleware): + def subscribe(self): """Create and return a subscription decorator for this topic.""" - self.server = FunctionServer(SubscriptionWorkerOptions(topic=self.name)) - self.server.event(func) - Nitric._register_worker(self.server) + + def decorator(func: EventMiddleware): + self.server = FunctionServer(SubscriptionWorkerOptions(topic=self.name)) + self.server.event(func) + Nitric._register_worker(self.server) + + return decorator def topic(name: str) -> Topic: @@ -171,7 +174,6 @@

              Classes

              name: str actions: List[Action] - server: FunctionServer def __init__(self, name: str): """Construct a new topic.""" @@ -205,11 +207,15 @@

              Classes

              return Events().topic(self.name) - def subscribe(self, func: EventMiddleware): + def subscribe(self): """Create and return a subscription decorator for this topic.""" - self.server = FunctionServer(SubscriptionWorkerOptions(topic=self.name)) - self.server.event(func) - Nitric._register_worker(self.server)
              + + def decorator(func: EventMiddleware): + self.server = FunctionServer(SubscriptionWorkerOptions(topic=self.name)) + self.server.event(func) + Nitric._register_worker(self.server) + + return decorator

              Ancestors

                @@ -227,10 +233,6 @@

                Class variables

                -
                var serverFunctionServer
                -
                -
                -

                Methods

                @@ -251,7 +253,7 @@

                Methods

                -def subscribe(self, func: EventMiddleware) +def subscribe(self)

                Create and return a subscription decorator for this topic.

                @@ -259,11 +261,15 @@

                Methods

                Expand source code -
                def subscribe(self, func: EventMiddleware):
                +
                def subscribe(self):
                     """Create and return a subscription decorator for this topic."""
                -    self.server = FunctionServer(SubscriptionWorkerOptions(topic=self.name))
                -    self.server.event(func)
                -    Nitric._register_worker(self.server)
                + + def decorator(func: EventMiddleware): + self.server = FunctionServer(SubscriptionWorkerOptions(topic=self.name)) + self.server.event(func) + Nitric._register_worker(self.server) + + return decorator
                @@ -330,7 +336,6 @@

                actions
              • allow
              • name
              • -
              • server
              • subscribe
              diff --git a/makefile b/makefile index b7cba44..4d652ae 100644 --- a/makefile +++ b/makefile @@ -1,6 +1,6 @@ install: @echo Installing Project Dependencies - @pip3 install -e .[dev] + @python3 -m pip install -e .[dev] @pre-commit install .PHONY: docs clean license @@ -16,7 +16,11 @@ clean: @rm -rf ./build @rm -rf ./dist -NITRIC_VERSION="v0.24.0-rc.7" +test: + @echo Running Tox tests + @tox -e py + +NITRIC_VERSION="v0.27.0" download: @curl -L https://github.com/nitrictech/nitric/releases/download/${NITRIC_VERSION}/contracts.tgz -o contracts.tgz diff --git a/nitric/api/documents.py b/nitric/api/documents.py index b20f04f..bf12b7b 100644 --- a/nitric/api/documents.py +++ b/nitric/api/documents.py @@ -25,7 +25,7 @@ from grpclib import GRPCError from nitric.api.const import MAX_SUB_COLLECTION_DEPTH -from nitric.api.exception import exception_from_grpc_error +from nitric.exception import exception_from_grpc_error from nitric.proto.nitric.document.v1 import ( DocumentServiceStub, Collection as CollectionMessage, diff --git a/nitric/api/events.py b/nitric/api/events.py index 6993ca6..46eda62 100644 --- a/nitric/api/events.py +++ b/nitric/api/events.py @@ -22,7 +22,7 @@ from grpclib import GRPCError -from nitric.api.exception import exception_from_grpc_error +from nitric.exception import exception_from_grpc_error from nitric.utils import new_default_channel, _struct_from_dict from nitric.proto.nitric.event.v1 import ( EventServiceStub, diff --git a/nitric/api/queues.py b/nitric/api/queues.py index a002c8b..99319f5 100644 --- a/nitric/api/queues.py +++ b/nitric/api/queues.py @@ -22,7 +22,7 @@ from grpclib import GRPCError -from nitric.api.exception import FailedPreconditionException, exception_from_grpc_error, InvalidArgumentException +from nitric.exception import FailedPreconditionException, exception_from_grpc_error, InvalidArgumentException from nitric.utils import new_default_channel, _struct_from_dict, _dict_from_struct from nitric.proto.nitric.queue.v1 import ( QueueServiceStub, diff --git a/nitric/api/secrets.py b/nitric/api/secrets.py index 7c9a85c..ee85e88 100644 --- a/nitric/api/secrets.py +++ b/nitric/api/secrets.py @@ -22,7 +22,7 @@ from grpclib import GRPCError -from nitric.api.exception import exception_from_grpc_error +from nitric.exception import exception_from_grpc_error from nitric.utils import new_default_channel from nitric.proto.nitric.secret.v1 import ( SecretServiceStub, diff --git a/nitric/api/storage.py b/nitric/api/storage.py index 0b39349..8649020 100644 --- a/nitric/api/storage.py +++ b/nitric/api/storage.py @@ -17,9 +17,12 @@ # limitations under the License. # from dataclasses import dataclass +from typing import Union from grpclib import GRPCError -from nitric.api.exception import exception_from_grpc_error, InvalidArgumentException +from nitric.exception import exception_from_grpc_error, InvalidArgumentException +from nitric.application import Nitric +from nitric.faas import FunctionServer, FileNotificationWorkerOptions, FileNotificationMiddleware from nitric.utils import new_default_channel from nitric.proto.nitric.storage.v1 import ( StorageServiceStub, @@ -53,15 +56,16 @@ def __del__(self): def bucket(self, name: str): """Return a reference to a bucket from the connected storage service.""" - return BucketRef(_storage=self, name=name) + return BucketRef(_storage=self, name=name, _server=None) -@dataclass(frozen=True, order=True) +@dataclass(order=True) class BucketRef(object): """A reference to a bucket in a storage service, used to the perform operations on that bucket.""" _storage: Storage name: str + _server: Union[FunctionServer, None] def file(self, key: str): """Return a reference to a file in this bucket.""" @@ -74,6 +78,22 @@ async def files(self): ) return [self.file(f.key) for f in resp.files] + def on(self, notification_type: str, notification_prefix_filter: str): + """Create and return a bucket notification decorator for this bucket.""" + + def decorator(func: FileNotificationMiddleware): + self._server = FunctionServer( + FileNotificationWorkerOptions( + bucket=self, + notification_type=notification_type, + notification_prefix_filter=notification_prefix_filter, + ) + ) + self._server.bucket_notification(func) + Nitric._register_worker(self._server) + + return decorator + class FileMode(Enum): """Definition of available operation modes for file signed URLs.""" diff --git a/nitric/application.py b/nitric/application.py index 90c0ecd..50e724a 100644 --- a/nitric/application.py +++ b/nitric/application.py @@ -26,7 +26,7 @@ from opentelemetry.instrumentation.grpc import GrpcInstrumentorClient from nitric.faas import FunctionServer -from nitric.api.exception import NitricUnavailableException +from nitric.exception import NitricUnavailableException # from nitric.resources.base import BaseResource from typing import Dict, List, Type, Any, TypeVar @@ -95,6 +95,7 @@ def run(cls): This will execute in an existing event loop if there is one, otherwise it will attempt to create its own. """ provider = cls._create_tracer() + print(cls._workers) try: try: loop = asyncio.get_running_loop() diff --git a/nitric/api/exception.py b/nitric/exception.py similarity index 100% rename from nitric/api/exception.py rename to nitric/exception.py diff --git a/nitric/faas.py b/nitric/faas.py index 3828ed3..4802ced 100644 --- a/nitric/faas.py +++ b/nitric/faas.py @@ -41,6 +41,10 @@ ApiWorker, SubscriptionWorker, ScheduleRate, + BucketNotificationWorker, + BucketNotificationConfig, + BucketNotificationType, + NotificationResponseContext, ) import grpclib import asyncio @@ -67,9 +71,10 @@ def __str__(self): class Request(ABC): """Represents an abstract trigger request.""" - def __init__(self, data: bytes): + def __init__(self, data: bytes, trace_context: Dict[str, str]): """Construct a new Request.""" self.data = data + self.trace_context = trace_context class Response(ABC): @@ -89,14 +94,23 @@ def event(self) -> Union[EventContext, None]: """Return this context as an EventContext if it is one, otherwise returns None.""" return None + def bucket_notification(self) -> Union[BucketNotificationContext, None]: + """Return this context as a BucketNotificationContext if it is one, otherwise returns None.""" + return None + -def _ctx_from_grpc_trigger_request(trigger_request: TriggerRequest): +def _ctx_from_grpc_trigger_request(trigger_request: TriggerRequest, options: FaasWorkerOptions = None): """Return a TriggerContext from a TriggerRequest.""" - context_type, context = betterproto.which_one_of(trigger_request, "context") + context_type, _ = betterproto.which_one_of(trigger_request, "context") if context_type == "http": return HttpContext.from_grpc_trigger_request(trigger_request) elif context_type == "topic": return EventContext.from_grpc_trigger_request(trigger_request) + elif context_type == "notification": + if isinstance(options, FileNotificationWorkerOptions): + return FileNotificationContext.from_grpc_trigger_request(trigger_request, options) + else: + return BucketNotificationContext.from_grpc_trigger_request(trigger_request) else: print(f"Trigger with unknown context received, context type: {context_type}") raise Exception(f"Unknown trigger context, type: {context_type}") @@ -126,6 +140,9 @@ def _grpc_response_from_ctx(ctx: TriggerContext) -> TriggerResponse: success=ctx.res.success, ), ) + elif ctx.bucket_notification(): + ctx = ctx.bucket_notification() + return TriggerResponse(notification=NotificationResponseContext(success=ctx.res.success)) else: raise Exception("Unknown Trigger Context type, unable to return valid response") @@ -147,13 +164,12 @@ def __init__( trace_context: Dict[str, str], ): """Construct a new HttpRequest.""" - super().__init__(data) + super().__init__(data, trace_context) self.method = method self.path = path self.params = params self.query = query self.headers = headers - self.trace_context = trace_context @property def json(self) -> Optional[Any]: @@ -240,9 +256,8 @@ class EventRequest(Request): def __init__(self, data: bytes, topic: str, trace_context: Dict[str, str]): """Construct a new EventRequest.""" - super().__init__(data) + super().__init__(data, trace_context) self.topic = topic - self.trace_context = trace_context @property def payload(self) -> bytes: @@ -283,83 +298,94 @@ def from_grpc_trigger_request(trigger_request: TriggerRequest): ) -# async def face(inpp: int) -> str: -# return "thing" +class BucketNotificationRequest(Request): + """Represents a translated Event, from a subscribed bucket notification, forwarded from the Nitric Membrane.""" + def __init__(self, data: bytes, key: str, notification_type: BucketNotificationType, trace_context: Dict[str, str]): + """Construct a new EventRequest.""" + super().__init__(data, trace_context) -# ====== Function Handlers ====== + self.key = key + self.notification_type = notification_type -C = TypeVar("C", TriggerContext, HttpContext, EventContext) -Middleware = Callable -Handler = Coroutine[Any, Any, C] -HttpHandler = Coroutine[Any, Any, Optional[HttpContext]] -EventHandler = Coroutine[Any, Any, Optional[EventContext]] -Middleware = Callable[[C, Middleware], Handler] -HttpMiddleware = Callable[[HttpContext, HttpHandler], HttpHandler] -EventMiddleware = Callable[[EventContext, EventHandler], EventHandler] +class BucketNotificationResponse(Response): + """Represents the response to a trigger from a Bucket.""" -def compose_middleware(*middlewares: Union[Middleware, List[Middleware]]) -> Middleware: - """ - Compose multiple middleware functions into a single middleware function. + def __init__(self, success: bool = True): + """Construct a new BucketNotificationResponse.""" + self.success = success - The resulting middleware will effectively be a chain of the provided middleware, - where each calls the next in the chain when they're successful. - """ - middlewares = list(middlewares) - if len(middlewares) == 1 and not isinstance(middlewares[0], list): - return middlewares[0] - middlewares = [compose_middleware(m) if isinstance(m, list) else m for m in middlewares] +class BucketNotificationContext(TriggerContext): + """Represents the full request/response context for a bucket notification trigger.""" - async def handler(ctx, next_middleware=lambda ctx: ctx): - def reduce_chain(acc_next, cur): - async def chained_middleware(context): - # Count the positional arguments to determine if the function is a handler or middleware. - all_args = cur.__code__.co_argcount - kwargs = len(cur.__defaults__) if cur.__defaults__ is not None else 0 - pos_args = all_args - kwargs - if pos_args == 2: - # Call the middleware with next and return the result - return ( - (await cur(context, acc_next)) if asyncio.iscoroutinefunction(cur) else cur(context, acc_next) - ) - else: - # Call the handler with ctx only, then call the remainder of the middleware chain - result = (await cur(context)) if asyncio.iscoroutinefunction(cur) else cur(context) - return (await acc_next(result)) if asyncio.iscoroutinefunction(acc_next) else acc_next(result) + def __init__(self, request: BucketNotificationRequest, response: BucketNotificationResponse = None): + """Construct a new BucketNotificationContext.""" + super().__init__() + self.req = request + self.res = response if response else BucketNotificationResponse() - return chained_middleware + def bucket_notification(self) -> BucketNotificationContext: + """Return this BucketNotificationContext, used when determining the context type of a trigger.""" + return self - middleware_chain = functools.reduce(reduce_chain, reversed(middlewares + [next_middleware])) - return await middleware_chain(ctx) + @staticmethod + def from_grpc_trigger_request(trigger_request: TriggerRequest) -> BucketNotificationContext: + """Construct a new BucketNotificationContext from a Bucket Notification trigger from the Nitric Membrane.""" + return BucketNotificationContext( + request=BucketNotificationRequest( + data=trigger_request.data, + key=trigger_request.notification.bucket.key, + notification_type=trigger_request.notification.bucket.type, + trace_context=trigger_request.trace_context.values, + ) + ) - return handler +class FileNotificationRequest(BucketNotificationRequest): + """Represents a translated Event, from a subscribed bucket notification, forwarded from the Nitric Membrane.""" -# ====== Function Server ====== + def __init__( + self, + data: bytes, + bucket_ref: Any, # can't import BucketRef due to circular dependency problems + key: str, + notification_type: BucketNotificationType, + trace_context: Dict[str, str], + ): + """Construct a new FileNotificationRequest.""" + super().__init__(data=data, key=key, notification_type=notification_type, trace_context=trace_context) + self.file = bucket_ref.file(key) -def _create_internal_error_response(req: TriggerRequest) -> TriggerResponse: - """Create a general error response based on the trigger request type.""" - context_type, context = betterproto.which_one_of(req, "context") - if context_type == "http": - return TriggerResponse(data=bytes(), http=HttpResponseContext(status=500)) - elif context_type == "topic": - return TriggerResponse(data=bytes(), topic=TopicResponseContext(success=False)) - else: - raise Exception(f"Unknown trigger type: {context_type}, unable to generate expected response") +class FileNotificationContext(TriggerContext): + """Represents the full request/response context for a bucket notification trigger.""" + def __init__(self, request: FileNotificationRequest, response: BucketNotificationResponse = None): + """Construct a new FileNotificationContext.""" + super().__init__() + self.req = request + self.res = response if response else BucketNotificationResponse() -class ApiWorkerOptions: - """Options for API workers.""" + def bucket_notification(self) -> FileNotificationContext: + """Return this FileNotificationContext, used when determining the context type of a trigger.""" + return self - def __init__(self, api: str, route: str, methods: List[Union[str, HttpMethod]], opts: MethodOptions): - """Construct a new options object.""" - self.api = api - self.route = route - self.methods = [str(method) for method in methods] - self.opts = opts + @staticmethod + def from_grpc_trigger_request( + trigger_request: TriggerRequest, options: FileNotificationWorkerOptions + ) -> FileNotificationContext: + """Construct a new FileNotificationTrigger from a Bucket Notification trigger from the Nitric Membrane.""" + return FileNotificationContext( + request=FileNotificationRequest( + data=trigger_request.data, + key=trigger_request.notification.bucket.key, + bucket_ref=options.bucket_ref, + notification_type=trigger_request.notification.bucket.type, + trace_context=trigger_request.trace_context.values, + ) + ) class RateWorkerOptions: @@ -376,6 +402,56 @@ def __init__(self, description: str, rate: int, frequency: Frequency): self.frequency = frequency +class BucketNotificationWorkerOptions: + """Options for bucket notification workers.""" + + def __init__(self, bucket_name: str, notification_type: str, notification_prefix_filter: str): + """Construct a new options object.""" + self.bucket_name = bucket_name + self.notification_type = BucketNotificationWorkerOptions._to_grpc_event_type(notification_type.lower()) + self.notification_prefix_filter = notification_prefix_filter + + @staticmethod + def _to_grpc_event_type(event_type: str) -> BucketNotificationType: + if event_type == "write": + return BucketNotificationType.Created + elif event_type == "delete": + return BucketNotificationType.Deleted + else: + raise ValueError(f"Event type {event_type} is unsupported") + + +class FileNotificationWorkerOptions(BucketNotificationWorkerOptions): + """Options for bucket notification workers with file references.""" + + def __init__(self, bucket, notification_type: str, notification_prefix_filter: str): + """Construct a new FileNotificationWorkerOptions.""" + super().__init__(bucket.name, notification_type, notification_prefix_filter) + + self.bucket_ref = bucket + + +class ApiWorkerOptions: + """Options for API workers.""" + + def __init__(self, api: str, route: str, methods: List[Union[str, HttpMethod]], opts: MethodOptions): + """Construct a new options object.""" + self.api = api + self.route = route + self.methods = [str(method) for method in methods] + self.opts = opts + + +class MethodOptions: + """Represents options when defining a method handler.""" + + security: dict[str, List[str]] + + def __init__(self, security: dict[str, List[str]] = None): + """Construct a new HTTP method options object.""" + self.security = security + + class SubscriptionWorkerOptions: """Options for subscription workers.""" @@ -406,23 +482,88 @@ def as_str_list() -> List[str]: return [str(frequency.value) for frequency in Frequency] -class MethodOptions: - """Represents options when defining a method handler.""" +class FaasWorkerOptions: + """Empty worker options for generic function handlers.""" - security: dict[str, List[str]] + pass - def __init__(self, security: dict[str, List[str]] = None): - """Construct a new HTTP method options object.""" - self.security = security +FaasClientOptions = Union[ + ApiWorkerOptions, + RateWorkerOptions, + SubscriptionWorkerOptions, + BucketNotificationWorkerOptions, + FileNotificationWorkerOptions, + FaasWorkerOptions, +] -class FaasWorkerOptions: - """Empty worker options for generic function handlers.""" - pass +# Defining Function Handlers and Middleware +C = TypeVar("C", TriggerContext, HttpContext, EventContext) +Middleware = Callable +Handler = Coroutine[Any, Any, C] +HttpHandler = Coroutine[Any, Any, Optional[HttpContext]] +EventHandler = Coroutine[Any, Any, Optional[EventContext]] +BucketNotificationHandler = Coroutine[Any, Any, Optional[BucketNotificationContext]] +FileNotificationHandler = Coroutine[Any, Any, Optional[FileNotificationContext]] + +Middleware = Callable[[C, Middleware], Handler] +HttpMiddleware = Callable[[HttpContext, HttpHandler], HttpHandler] +EventMiddleware = Callable[[EventContext, EventHandler], EventHandler] +BucketNotificationMiddleware = Callable[ + [BucketNotificationContext, BucketNotificationHandler], BucketNotificationHandler +] +FileNotificationMiddleware = Callable[[FileNotificationContext, FileNotificationHandler], FileNotificationHandler] + +def compose_middleware(*middlewares: Union[Middleware, List[Middleware]]) -> Middleware: + """ + Compose multiple middleware functions into a single middleware function. -FaasClientOptions = Union[ApiWorkerOptions, RateWorkerOptions, SubscriptionWorkerOptions, FaasWorkerOptions] + The resulting middleware will effectively be a chain of the provided middleware, + where each calls the next in the chain when they're successful. + """ + middlewares = list(middlewares) + if len(middlewares) == 1 and not isinstance(middlewares[0], list): + return middlewares[0] + + middlewares = [compose_middleware(m) if isinstance(m, list) else m for m in middlewares] + + async def handler(ctx, next_middleware=lambda ctx: ctx): + def reduce_chain(acc_next, cur): + async def chained_middleware(mw_ctx): + # Count the positional arguments to determine if the function is a handler or middleware. + all_args = cur.__code__.co_argcount + kwargs = len(cur.__defaults__) if cur.__defaults__ is not None else 0 + pos_args = all_args - kwargs + if pos_args == 2: + # Call the middleware with next and return the result + return (await cur(mw_ctx, acc_next)) if asyncio.iscoroutinefunction(cur) else cur(mw_ctx, acc_next) + else: + # Call the handler with ctx only, then call the remainder of the middleware chain + result = (await cur(mw_ctx)) if asyncio.iscoroutinefunction(cur) else cur(mw_ctx) + return (await acc_next(result)) if asyncio.iscoroutinefunction(acc_next) else acc_next(result) + + return chained_middleware + + middleware_chain = functools.reduce(reduce_chain, reversed(middlewares + [next_middleware])) + return await middleware_chain(ctx) + + return handler + + +# ====== Function Server ====== + + +def _create_internal_error_response(req: TriggerRequest) -> TriggerResponse: + """Create a general error response based on the trigger request type.""" + context_type, ctx = betterproto.which_one_of(req, "context") + if context_type == "http": + return TriggerResponse(data=bytes(), http=HttpResponseContext(status=500)) + elif context_type == "topic": + return TriggerResponse(data=bytes(), topic=TopicResponseContext(success=False)) + else: + raise Exception(f"Unknown trigger type: {context_type}, unable to generate expected response") class FunctionServer: @@ -432,6 +573,7 @@ def __init__(self, opts: FaasClientOptions): """Construct a new function server.""" self.__http_handler = None self.__event_handler = None + self.__bucket_notification_handler = None self._any_handler = None self._opts = opts @@ -453,10 +595,24 @@ def event(self, *handlers: Union[Middleware, List[Middleware]]) -> FunctionServe self.__event_handler = compose_middleware(*handlers) return self + def bucket_notification(self, *handlers: Union[Middleware, List[Middleware]]) -> FunctionServer: + """ + Register one or more Bucket Notification Trigger Handlers or Middleware. + + When multiple handlers are provided, they will be called in order. + """ + self.__bucket_notification_handler = compose_middleware(*handlers) + return self + async def start(self, *handlers: Union[Middleware, List[Middleware]]): """Start the function server using the provided trigger handlers.""" self._any_handler = compose_middleware(*handlers) if len(handlers) > 0 else None - if not self._any_handler and not self._http_handler and not self._event_handler: + if ( + not self._any_handler + and not self._http_handler + and not self._event_handler + and not self.__bucket_notification_handler + ): raise Exception("At least one handler function must be provided.") await self._run() @@ -469,6 +625,10 @@ def _http_handler(self): def _event_handler(self): return self.__event_handler if self.__event_handler else self._any_handler + @property + def _bucket_notification_handler(self): + return self.__bucket_notification_handler if self.__bucket_notification_handler else self._any_handler + async def _run(self): """Register a new FaaS worker with the Membrane, using the provided function as the handler.""" channel = new_default_channel() @@ -491,6 +651,16 @@ async def _run(self): ) elif isinstance(self._opts, SubscriptionWorkerOptions): init_request = InitRequest(subscription=SubscriptionWorker(topic=self._opts.topic)) + elif isinstance(self._opts, BucketNotificationWorkerOptions) or isinstance( + self._opts, FileNotificationWorkerOptions + ): + config = BucketNotificationConfig( + notification_type=self._opts.notification_type, + notification_prefix_filter=self._opts.notification_prefix_filter, + ) + init_request = InitRequest( + bucket_notification=BucketNotificationWorker(bucket=self._opts.bucket_name, config=config) + ) # let the membrane server know we're ready to start await request_channel.send(ClientMessage(init_request=init_request)) @@ -504,7 +674,7 @@ async def _run(self): # proceed to the next available message continue if msg_type == "trigger_request": - ctx = _ctx_from_grpc_trigger_request(srv_msg.trigger_request) + ctx = _ctx_from_grpc_trigger_request(srv_msg.trigger_request, self._opts) try: if len(ctx.req.trace_context) > 0: @@ -514,6 +684,8 @@ async def _run(self): func = self._http_handler elif ctx.event(): func = self._event_handler + elif ctx.bucket_notification(): + func = self._bucket_notification_handler else: func = self._any_handler @@ -579,6 +751,15 @@ def event(*handlers: Union[Middleware, List[Middleware]]) -> FunctionServer: return FunctionServer(opts=[]).event(*handlers) +def bucket_notification(*handlers: Union[Middleware, List[Middleware]]) -> FunctionServer: + """ + Create a new Function Server and Register one or more Bucket Notification Handlers or Middleware. + + When multiple handlers are provided, they will be called in order. + """ + return FunctionServer(opts=[]).bucket_notification(*handlers) + + def start(*handlers: Union[Middleware, List[Middleware]]): """Create a new Function Server and start it using the provided trigger handlers.""" if len(handlers) < 1: diff --git a/nitric/proto/__init__.py b/nitric/proto/__init__.py index e69de29..4eb07ea 100644 --- a/nitric/proto/__init__.py +++ b/nitric/proto/__init__.py @@ -0,0 +1,18 @@ +# +# Copyright (c) 2021 Nitric Technologies Pty Ltd. +# +# This file is part of Nitric Python 3 SDK. +# See https://github.com/nitrictech/python-sdk for further info. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# diff --git a/nitric/proto/nitric/__init__.py b/nitric/proto/nitric/__init__.py index e69de29..4eb07ea 100644 --- a/nitric/proto/nitric/__init__.py +++ b/nitric/proto/nitric/__init__.py @@ -0,0 +1,18 @@ +# +# Copyright (c) 2021 Nitric Technologies Pty Ltd. +# +# This file is part of Nitric Python 3 SDK. +# See https://github.com/nitrictech/python-sdk for further info. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# diff --git a/nitric/proto/nitric/deploy/__init__.py b/nitric/proto/nitric/deploy/__init__.py index e69de29..4eb07ea 100644 --- a/nitric/proto/nitric/deploy/__init__.py +++ b/nitric/proto/nitric/deploy/__init__.py @@ -0,0 +1,18 @@ +# +# Copyright (c) 2021 Nitric Technologies Pty Ltd. +# +# This file is part of Nitric Python 3 SDK. +# See https://github.com/nitrictech/python-sdk for further info. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# diff --git a/nitric/proto/nitric/deploy/v1/__init__.py b/nitric/proto/nitric/deploy/v1/__init__.py index 5e252a2..01dec17 100644 --- a/nitric/proto/nitric/deploy/v1/__init__.py +++ b/nitric/proto/nitric/deploy/v1/__init__.py @@ -1,3 +1,22 @@ +# +# Copyright (c) 2021 Nitric Technologies Pty Ltd. +# +# This file is part of Nitric Python 3 SDK. +# See https://github.com/nitrictech/python-sdk for further info. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + # Generated by the protocol buffer compiler. DO NOT EDIT! # sources: proto/deploy/v1/deploy.proto # plugin: python-betterproto @@ -16,6 +35,7 @@ import grpclib from betterproto.grpc.grpclib_server import ServiceBase +from ...faas import v1 as __faas_v1__ from ...resource import v1 as __resource_v1__ @@ -141,7 +161,14 @@ def __post_init__(self) -> None: @dataclass(eq=False, repr=False) class Bucket(betterproto.Message): - pass + notifications: List["BucketNotificationTarget"] = betterproto.message_field(1) + + +@dataclass(eq=False, repr=False) +class BucketNotificationTarget(betterproto.Message): + config: "__faas_v1__.BucketNotificationConfig" = betterproto.message_field(1) + execution_unit: str = betterproto.string_field(2, group="target") + """The name of an execution unit to target""" @dataclass(eq=False, repr=False) diff --git a/nitric/proto/nitric/document/__init__.py b/nitric/proto/nitric/document/__init__.py index e69de29..4eb07ea 100644 --- a/nitric/proto/nitric/document/__init__.py +++ b/nitric/proto/nitric/document/__init__.py @@ -0,0 +1,18 @@ +# +# Copyright (c) 2021 Nitric Technologies Pty Ltd. +# +# This file is part of Nitric Python 3 SDK. +# See https://github.com/nitrictech/python-sdk for further info. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# diff --git a/nitric/proto/nitric/document/v1/__init__.py b/nitric/proto/nitric/document/v1/__init__.py index 0321bc6..3b1cf26 100644 --- a/nitric/proto/nitric/document/v1/__init__.py +++ b/nitric/proto/nitric/document/v1/__init__.py @@ -1,3 +1,22 @@ +# +# Copyright (c) 2021 Nitric Technologies Pty Ltd. +# +# This file is part of Nitric Python 3 SDK. +# See https://github.com/nitrictech/python-sdk for further info. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + # Generated by the protocol buffer compiler. DO NOT EDIT! # sources: proto/document/v1/document.proto # plugin: python-betterproto diff --git a/nitric/proto/nitric/error/__init__.py b/nitric/proto/nitric/error/__init__.py index e69de29..4eb07ea 100644 --- a/nitric/proto/nitric/error/__init__.py +++ b/nitric/proto/nitric/error/__init__.py @@ -0,0 +1,18 @@ +# +# Copyright (c) 2021 Nitric Technologies Pty Ltd. +# +# This file is part of Nitric Python 3 SDK. +# See https://github.com/nitrictech/python-sdk for further info. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# diff --git a/nitric/proto/nitric/error/v1/__init__.py b/nitric/proto/nitric/error/v1/__init__.py index 09e6ec8..71de5c7 100644 --- a/nitric/proto/nitric/error/v1/__init__.py +++ b/nitric/proto/nitric/error/v1/__init__.py @@ -1,3 +1,22 @@ +# +# Copyright (c) 2021 Nitric Technologies Pty Ltd. +# +# This file is part of Nitric Python 3 SDK. +# See https://github.com/nitrictech/python-sdk for further info. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + # Generated by the protocol buffer compiler. DO NOT EDIT! # sources: proto/error/v1/error.proto # plugin: python-betterproto diff --git a/nitric/proto/nitric/event/__init__.py b/nitric/proto/nitric/event/__init__.py index e69de29..4eb07ea 100644 --- a/nitric/proto/nitric/event/__init__.py +++ b/nitric/proto/nitric/event/__init__.py @@ -0,0 +1,18 @@ +# +# Copyright (c) 2021 Nitric Technologies Pty Ltd. +# +# This file is part of Nitric Python 3 SDK. +# See https://github.com/nitrictech/python-sdk for further info. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# diff --git a/nitric/proto/nitric/event/v1/__init__.py b/nitric/proto/nitric/event/v1/__init__.py index cd16894..af1b5bb 100644 --- a/nitric/proto/nitric/event/v1/__init__.py +++ b/nitric/proto/nitric/event/v1/__init__.py @@ -1,3 +1,22 @@ +# +# Copyright (c) 2021 Nitric Technologies Pty Ltd. +# +# This file is part of Nitric Python 3 SDK. +# See https://github.com/nitrictech/python-sdk for further info. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + # Generated by the protocol buffer compiler. DO NOT EDIT! # sources: proto/event/v1/event.proto # plugin: python-betterproto diff --git a/nitric/proto/nitric/faas/__init__.py b/nitric/proto/nitric/faas/__init__.py index e69de29..4eb07ea 100644 --- a/nitric/proto/nitric/faas/__init__.py +++ b/nitric/proto/nitric/faas/__init__.py @@ -0,0 +1,18 @@ +# +# Copyright (c) 2021 Nitric Technologies Pty Ltd. +# +# This file is part of Nitric Python 3 SDK. +# See https://github.com/nitrictech/python-sdk for further info. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# diff --git a/nitric/proto/nitric/faas/v1/__init__.py b/nitric/proto/nitric/faas/v1/__init__.py index afe2713..2f87728 100644 --- a/nitric/proto/nitric/faas/v1/__init__.py +++ b/nitric/proto/nitric/faas/v1/__init__.py @@ -1,3 +1,22 @@ +# +# Copyright (c) 2021 Nitric Technologies Pty Ltd. +# +# This file is part of Nitric Python 3 SDK. +# See https://github.com/nitrictech/python-sdk for further info. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + # Generated by the protocol buffer compiler. DO NOT EDIT! # sources: proto/faas/v1/faas.proto # plugin: python-betterproto @@ -25,6 +44,14 @@ from grpclib.metadata import Deadline +class BucketNotificationType(betterproto.Enum): + """Notification Workers""" + + All = 0 + Created = 1 + Deleted = 2 + + @dataclass(eq=False, repr=False) class ClientMessage(betterproto.Message): """Messages the client is able to send to the server""" @@ -108,6 +135,24 @@ class ScheduleCron(betterproto.Message): cron: str = betterproto.string_field(1) +@dataclass(eq=False, repr=False) +class BucketNotificationWorker(betterproto.Message): + bucket: str = betterproto.string_field(1) + config: "BucketNotificationConfig" = betterproto.message_field(2) + + +@dataclass(eq=False, repr=False) +class BucketNotificationConfig(betterproto.Message): + notification_type: "BucketNotificationType" = betterproto.enum_field(1) + notification_prefix_filter: str = betterproto.string_field(2) + """ + A notification filter is a prefix for a bucket object in which creations or + deletions should trigger a notification: e.g. Notification filter: + /images/cat and Event Type: created, would trigger on creating + /images/cat.png and /images/cat.jpg but not creating /cat.png + """ + + @dataclass(eq=False, repr=False) class InitRequest(betterproto.Message): """ @@ -119,6 +164,9 @@ class InitRequest(betterproto.Message): api: "ApiWorker" = betterproto.message_field(10, group="Worker") subscription: "SubscriptionWorker" = betterproto.message_field(11, group="Worker") schedule: "ScheduleWorker" = betterproto.message_field(12, group="Worker") + bucket_notification: "BucketNotificationWorker" = betterproto.message_field( + 13, group="Worker" + ) @dataclass(eq=False, repr=False) @@ -155,6 +203,9 @@ class TriggerRequest(betterproto.Message): http: "HttpTriggerContext" = betterproto.message_field(3, group="context") topic: "TopicTriggerContext" = betterproto.message_field(4, group="context") + notification: "NotificationTriggerContext" = betterproto.message_field( + 5, group="context" + ) @dataclass(eq=False, repr=False) @@ -224,6 +275,18 @@ class TopicTriggerContext(betterproto.Message): """The topic the message was published for""" +@dataclass(eq=False, repr=False) +class BucketNotification(betterproto.Message): + key: str = betterproto.string_field(1) + type: "BucketNotificationType" = betterproto.enum_field(2) + + +@dataclass(eq=False, repr=False) +class NotificationTriggerContext(betterproto.Message): + source: str = betterproto.string_field(1) + bucket: "BucketNotification" = betterproto.message_field(10, group="notification") + + @dataclass(eq=False, repr=False) class TriggerResponse(betterproto.Message): """The worker has successfully processed a trigger""" @@ -237,6 +300,11 @@ class TriggerResponse(betterproto.Message): topic: "TopicResponseContext" = betterproto.message_field(11, group="context") """response to a topic trigger""" + notification: "NotificationResponseContext" = betterproto.message_field( + 12, group="context" + ) + """response to a notification trigger""" + @dataclass(eq=False, repr=False) class HttpResponseContext(betterproto.Message): @@ -277,6 +345,11 @@ class TopicResponseContext(betterproto.Message): """Success status of the handled event""" +@dataclass(eq=False, repr=False) +class NotificationResponseContext(betterproto.Message): + success: bool = betterproto.bool_field(1) + + class FaasServiceStub(betterproto.ServiceStub): async def trigger_stream( self, diff --git a/nitric/proto/nitric/queue/__init__.py b/nitric/proto/nitric/queue/__init__.py index e69de29..4eb07ea 100644 --- a/nitric/proto/nitric/queue/__init__.py +++ b/nitric/proto/nitric/queue/__init__.py @@ -0,0 +1,18 @@ +# +# Copyright (c) 2021 Nitric Technologies Pty Ltd. +# +# This file is part of Nitric Python 3 SDK. +# See https://github.com/nitrictech/python-sdk for further info. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# diff --git a/nitric/proto/nitric/queue/v1/__init__.py b/nitric/proto/nitric/queue/v1/__init__.py index 3ceb3e3..039d7f9 100644 --- a/nitric/proto/nitric/queue/v1/__init__.py +++ b/nitric/proto/nitric/queue/v1/__init__.py @@ -1,3 +1,22 @@ +# +# Copyright (c) 2021 Nitric Technologies Pty Ltd. +# +# This file is part of Nitric Python 3 SDK. +# See https://github.com/nitrictech/python-sdk for further info. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + # Generated by the protocol buffer compiler. DO NOT EDIT! # sources: proto/queue/v1/queue.proto # plugin: python-betterproto diff --git a/nitric/proto/nitric/resource/__init__.py b/nitric/proto/nitric/resource/__init__.py index e69de29..4eb07ea 100644 --- a/nitric/proto/nitric/resource/__init__.py +++ b/nitric/proto/nitric/resource/__init__.py @@ -0,0 +1,18 @@ +# +# Copyright (c) 2021 Nitric Technologies Pty Ltd. +# +# This file is part of Nitric Python 3 SDK. +# See https://github.com/nitrictech/python-sdk for further info. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# diff --git a/nitric/proto/nitric/resource/v1/__init__.py b/nitric/proto/nitric/resource/v1/__init__.py index 8d62270..2cc9112 100644 --- a/nitric/proto/nitric/resource/v1/__init__.py +++ b/nitric/proto/nitric/resource/v1/__init__.py @@ -1,3 +1,22 @@ +# +# Copyright (c) 2021 Nitric Technologies Pty Ltd. +# +# This file is part of Nitric Python 3 SDK. +# See https://github.com/nitrictech/python-sdk for further info. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + # Generated by the protocol buffer compiler. DO NOT EDIT! # sources: proto/resource/v1/resource.proto # plugin: python-betterproto @@ -31,6 +50,7 @@ class ResourceType(betterproto.Enum): Collection = 7 Policy = 8 Secret = 9 + Notification = 10 class Action(betterproto.Enum): diff --git a/nitric/proto/nitric/secret/__init__.py b/nitric/proto/nitric/secret/__init__.py index e69de29..4eb07ea 100644 --- a/nitric/proto/nitric/secret/__init__.py +++ b/nitric/proto/nitric/secret/__init__.py @@ -0,0 +1,18 @@ +# +# Copyright (c) 2021 Nitric Technologies Pty Ltd. +# +# This file is part of Nitric Python 3 SDK. +# See https://github.com/nitrictech/python-sdk for further info. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# diff --git a/nitric/proto/nitric/secret/v1/__init__.py b/nitric/proto/nitric/secret/v1/__init__.py index c369d3d..e5e0553 100644 --- a/nitric/proto/nitric/secret/v1/__init__.py +++ b/nitric/proto/nitric/secret/v1/__init__.py @@ -1,3 +1,22 @@ +# +# Copyright (c) 2021 Nitric Technologies Pty Ltd. +# +# This file is part of Nitric Python 3 SDK. +# See https://github.com/nitrictech/python-sdk for further info. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + # Generated by the protocol buffer compiler. DO NOT EDIT! # sources: proto/secret/v1/secret.proto # plugin: python-betterproto diff --git a/nitric/proto/nitric/storage/__init__.py b/nitric/proto/nitric/storage/__init__.py index e69de29..4eb07ea 100644 --- a/nitric/proto/nitric/storage/__init__.py +++ b/nitric/proto/nitric/storage/__init__.py @@ -0,0 +1,18 @@ +# +# Copyright (c) 2021 Nitric Technologies Pty Ltd. +# +# This file is part of Nitric Python 3 SDK. +# See https://github.com/nitrictech/python-sdk for further info. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# diff --git a/nitric/proto/nitric/storage/v1/__init__.py b/nitric/proto/nitric/storage/v1/__init__.py index 6e6130e..6d86f0a 100644 --- a/nitric/proto/nitric/storage/v1/__init__.py +++ b/nitric/proto/nitric/storage/v1/__init__.py @@ -1,3 +1,22 @@ +# +# Copyright (c) 2021 Nitric Technologies Pty Ltd. +# +# This file is part of Nitric Python 3 SDK. +# See https://github.com/nitrictech/python-sdk for further info. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + # Generated by the protocol buffer compiler. DO NOT EDIT! # sources: proto/storage/v1/storage.proto # plugin: python-betterproto diff --git a/nitric/proto/validate/__init__.py b/nitric/proto/validate/__init__.py index 8b5dd42..685cbab 100644 --- a/nitric/proto/validate/__init__.py +++ b/nitric/proto/validate/__init__.py @@ -1,3 +1,22 @@ +# +# Copyright (c) 2021 Nitric Technologies Pty Ltd. +# +# This file is part of Nitric Python 3 SDK. +# See https://github.com/nitrictech/python-sdk for further info. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + # Generated by the protocol buffer compiler. DO NOT EDIT! # sources: validate/validate.proto # plugin: python-betterproto diff --git a/nitric/resources/apis.py b/nitric/resources/apis.py index b2da98f..c6dadfd 100644 --- a/nitric/resources/apis.py +++ b/nitric/resources/apis.py @@ -33,7 +33,7 @@ ResourceDetailsRequest, ) from grpclib import GRPCError -from nitric.api.exception import exception_from_grpc_error +from nitric.exception import exception_from_grpc_error @dataclass diff --git a/nitric/resources/base.py b/nitric/resources/base.py index 96a6a1a..75f4870 100644 --- a/nitric/resources/base.py +++ b/nitric/resources/base.py @@ -34,7 +34,7 @@ ResourceServiceStub, ) -from nitric.api.exception import exception_from_grpc_error, NitricResourceException +from nitric.exception import exception_from_grpc_error, NitricResourceException from nitric.utils import new_default_channel T = TypeVar("T", bound="BaseResource") @@ -43,8 +43,6 @@ class BaseResource(ABC): """A base resource class with common functionality.""" - cache = {} - def __init__(self): """Construct a new resource.""" self._reg: Union[Task, None] = None @@ -62,7 +60,6 @@ def make(cls: Type[T], name: str, *args, **kwargs) -> T: The registration process for resources async, so this method should be used instead of __init__. """ - # Todo: store the resource reference in a cache to avoid duplicate registrations r = cls(name, *args, **kwargs) try: loop = asyncio.get_running_loop() diff --git a/nitric/resources/buckets.py b/nitric/resources/buckets.py index d858779..6299baa 100644 --- a/nitric/resources/buckets.py +++ b/nitric/resources/buckets.py @@ -18,13 +18,14 @@ # from __future__ import annotations -from nitric.api.exception import exception_from_grpc_error +from nitric.exception import exception_from_grpc_error from nitric.api.storage import BucketRef, Storage from typing import List, Union from enum import Enum from grpclib import GRPCError from nitric.application import Nitric +from nitric.faas import FunctionServer, BucketNotificationWorkerOptions, BucketNotificationMiddleware from nitric.proto.nitric.resource.v1 import ( Resource, ResourceType, @@ -48,6 +49,7 @@ class Bucket(SecureResource): name: str actions: List[Action] + _server: FunctionServer def __init__(self, name: str): """Create a bucket with the name provided or references it if it already exists.""" @@ -84,6 +86,24 @@ def allow(self, *args: Union[BucketPermission, str]) -> BucketRef: return Storage().bucket(self.name) + def on(self, notification_type: str, notification_prefix_filter: str): + """Create and return a bucket notification decorator for this bucket.""" + print("this has been called") + + def decorator(func: BucketNotificationMiddleware): + print("this has been called") + self._server = FunctionServer( + BucketNotificationWorkerOptions( + bucket_name=self.name, + notification_type=notification_type, + notification_prefix_filter=notification_prefix_filter, + ) + ) + self._server.bucket_notification(func) + return Nitric._register_worker(self._server) + + return decorator + def bucket(name: str) -> Bucket: """ diff --git a/nitric/resources/collections.py b/nitric/resources/collections.py index 9323eda..2b8abf5 100644 --- a/nitric/resources/collections.py +++ b/nitric/resources/collections.py @@ -19,7 +19,7 @@ from __future__ import annotations from nitric.api.documents import CollectionRef, Documents -from nitric.api.exception import exception_from_grpc_error +from nitric.exception import exception_from_grpc_error from typing import List, Union from enum import Enum from grpclib import GRPCError diff --git a/nitric/resources/queues.py b/nitric/resources/queues.py index 36b49de..b98b232 100644 --- a/nitric/resources/queues.py +++ b/nitric/resources/queues.py @@ -18,7 +18,7 @@ # from __future__ import annotations -from nitric.api.exception import exception_from_grpc_error +from nitric.exception import exception_from_grpc_error from typing import List, Union from enum import Enum from grpclib import GRPCError diff --git a/nitric/resources/secrets.py b/nitric/resources/secrets.py index ebba11c..86200df 100644 --- a/nitric/resources/secrets.py +++ b/nitric/resources/secrets.py @@ -18,7 +18,7 @@ # from __future__ import annotations -from nitric.api.exception import exception_from_grpc_error +from nitric.exception import exception_from_grpc_error from typing import List, Union from enum import Enum from grpclib import GRPCError diff --git a/nitric/resources/topics.py b/nitric/resources/topics.py index ed8a4c9..b62fbe3 100644 --- a/nitric/resources/topics.py +++ b/nitric/resources/topics.py @@ -19,7 +19,7 @@ from __future__ import annotations from nitric.api.events import Events, TopicRef -from nitric.api.exception import exception_from_grpc_error +from nitric.exception import exception_from_grpc_error from typing import List, Union from enum import Enum from grpclib import GRPCError @@ -46,7 +46,6 @@ class Topic(SecureResource): name: str actions: List[Action] - server: FunctionServer def __init__(self, name: str): """Construct a new topic.""" @@ -80,11 +79,15 @@ def allow(self, *args: Union[TopicPermission, str]) -> TopicRef: return Events().topic(self.name) - def subscribe(self, func: EventMiddleware): + def subscribe(self): """Create and return a subscription decorator for this topic.""" - self.server = FunctionServer(SubscriptionWorkerOptions(topic=self.name)) - self.server.event(func) - Nitric._register_worker(self.server) + + def decorator(func: EventMiddleware): + self.server = FunctionServer(SubscriptionWorkerOptions(topic=self.name)) + self.server.event(func) + Nitric._register_worker(self.server) + + return decorator def topic(name: str) -> Topic: diff --git a/tests/api/test_documents.py b/tests/api/test_documents.py index 86c23d3..ed346c6 100644 --- a/tests/api/test_documents.py +++ b/tests/api/test_documents.py @@ -23,7 +23,6 @@ from betterproto.lib.google.protobuf import Struct, Value from grpclib import GRPCError, Status -from nitric.api import Events, Event from nitric.api.documents import ( QueryBuilder, Operator, @@ -33,7 +32,7 @@ Document as SdkDocument, QueryResultsPage, ) -from nitric.api.exception import UnknownException +from nitric.exception import UnknownException from nitric.proto.nitric.document.v1 import ( Key, Collection, @@ -49,7 +48,6 @@ DocumentQueryRequest, DocumentQueryStreamRequest, ) -from nitric.proto.nitric.event.v1 import TopicListResponse, NitricTopic class Object(object): diff --git a/tests/api/test_events.py b/tests/api/test_events.py index e8881ac..afed661 100644 --- a/tests/api/test_events.py +++ b/tests/api/test_events.py @@ -24,7 +24,7 @@ from grpclib import GRPCError, Status from nitric.api import Events, Event -from nitric.api.exception import UnknownException +from nitric.exception import UnknownException from nitric.proto.nitric.event.v1 import TopicListResponse, NitricTopic, EventPublishRequest, NitricEvent from nitric.utils import _struct_from_dict diff --git a/tests/api/test_queues.py b/tests/api/test_queues.py index b8cac8f..2c237f0 100644 --- a/tests/api/test_queues.py +++ b/tests/api/test_queues.py @@ -24,7 +24,7 @@ from grpclib import GRPCError, Status from nitric.api import Queues, Task -from nitric.api.exception import UnknownException +from nitric.exception import UnknownException from nitric.api.queues import ReceivedTask from nitric.proto.nitric.queue.v1 import ( QueueReceiveResponse, diff --git a/tests/api/test_secrets.py b/tests/api/test_secrets.py index 83d079e..f794b5e 100644 --- a/tests/api/test_secrets.py +++ b/tests/api/test_secrets.py @@ -23,7 +23,7 @@ from grpclib import GRPCError, Status from nitric.api import Secrets -from nitric.api.exception import UnknownException +from nitric.exception import UnknownException from nitric.api.secrets import SecretValue from nitric.proto.nitric.secret.v1 import ( SecretPutResponse, diff --git a/tests/api/test_storage.py b/tests/api/test_storage.py index b00c38d..24e2102 100644 --- a/tests/api/test_storage.py +++ b/tests/api/test_storage.py @@ -31,7 +31,7 @@ ) from nitric.api import Storage -from nitric.api.exception import UnknownException +from nitric.exception import UnknownException class Object(object): diff --git a/tests/resources/test_apis.py b/tests/resources/test_apis.py index 1a45f86..b7a9796 100644 --- a/tests/resources/test_apis.py +++ b/tests/resources/test_apis.py @@ -17,13 +17,13 @@ # limitations under the License. # from unittest import IsolatedAsyncioTestCase -from unittest.mock import patch, AsyncMock, Mock +from unittest.mock import patch, AsyncMock import pytest from grpclib import GRPCError, Status -from nitric.api.exception import InternalException -from nitric.faas import HttpMethod, MethodOptions, ApiWorkerOptions, HttpContext, HttpRequest +from nitric.exception import InternalException +from nitric.faas import HttpMethod, MethodOptions, ApiWorkerOptions from nitric.proto.nitric.resource.v1 import ( ResourceDeclareRequest, ApiResource, diff --git a/tests/test_application.py b/tests/test_application.py index 73574a9..55d0f03 100644 --- a/tests/test_application.py +++ b/tests/test_application.py @@ -1,29 +1,28 @@ -# # -# # Copyright (c) 2021 Nitric Technologies Pty Ltd. -# # -# # This file is part of Nitric Python 3 SDK. -# # See https://github.com/nitrictech/python-sdk for further info. -# # -# # Licensed under the Apache License, Version 2.0 (the "License"); -# # you may not use this file except in compliance with the License. -# # You may obtain a copy of the License at -# # -# # http://www.apache.org/licenses/LICENSE-2.0 -# # -# # Unless required by applicable law or agreed to in writing, software -# # distributed under the License is distributed on an "AS IS" BASIS, -# # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# # See the License for the specific language governing permissions and -# # limitations under the License. -# # +# +# Copyright (c) 2021 Nitric Technologies Pty Ltd. +# +# This file is part of Nitric Python 3 SDK. +# See https://github.com/nitrictech/python-sdk for further info. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# from unittest import IsolatedAsyncioTestCase from unittest.mock import patch, AsyncMock, Mock -from grpclib import GRPCError, Status -from opentelemetry.sdk.trace import TracerProvider, sampling +from opentelemetry.sdk.trace import sampling -from nitric.api.exception import NitricUnavailableException +from nitric.exception import NitricUnavailableException from nitric.resources import Bucket from nitric.application import Nitric @@ -60,7 +59,6 @@ def test_create_tracer(self): assert isinstance(tracer.sampler, sampling.TraceIdRatioBased) assert tracer.sampler.rate == 0.8 - def test_run(self): application = Nitric() mock_running_loop = Mock() diff --git a/tests/api/test_exception.py b/tests/test_exception.py similarity index 97% rename from tests/api/test_exception.py rename to tests/test_exception.py index 9476ae8..c0ee13b 100644 --- a/tests/api/test_exception.py +++ b/tests/test_exception.py @@ -16,12 +16,11 @@ # See the License for the specific language governing permissions and # limitations under the License. # -from unittest import TestCase import pytest from grpclib import GRPCError, Status -from nitric.api.exception import ( +from nitric.exception import ( CancelledException, exception_from_grpc_error, _exception_code_map, diff --git a/tests/test_faas.py b/tests/test_faas.py index de75f24..42dc8cd 100644 --- a/tests/test_faas.py +++ b/tests/test_faas.py @@ -17,11 +17,20 @@ # limitations under the License. # from unittest import IsolatedAsyncioTestCase -from unittest.mock import patch, AsyncMock, Mock, call +from unittest.mock import patch, AsyncMock, Mock, call, MagicMock import pytest -from nitric.faas import start, FunctionServer, HttpContext, compose_middleware, HttpResponse, FaasWorkerOptions +from nitric.faas import ( + start, + FunctionServer, + HttpContext, + compose_middleware, + HttpResponse, + FaasWorkerOptions, + BucketNotificationWorkerOptions, + FileNotificationWorkerOptions, +) from nitric.proto.nitric.faas.v1 import ( ServerMessage, @@ -31,6 +40,7 @@ TriggerRequest, TopicTriggerContext, HttpTriggerContext, + BucketNotificationType, ) @@ -151,6 +161,7 @@ async def mock_stream(self, request_iterator): async def test_trigger_sync_event_handler(self): mock_http_handler = Mock() mock_event_handler = Mock() + mock_bucket_notification_handler = Mock() mock_grpc_channel = Mock() mock_async_channel_init = Mock() mock_async_chan = MockAsyncChannel() @@ -176,16 +187,24 @@ async def mock_stream(self, request_iterator): with patch("nitric.faas.AsyncChannel", mock_async_channel_init), patch( "nitric.proto.nitric.faas.v1.FaasServiceStub.trigger_stream", mock_stream ), patch("nitric.faas.new_default_channel", mock_grpc_channel): - await FunctionServer(opts=FaasWorkerOptions()).http(mock_http_handler).event(mock_event_handler)._run() + await ( + FunctionServer(opts=FaasWorkerOptions()) + .http(mock_http_handler) + .event(mock_event_handler) + .bucket_notification(mock_bucket_notification_handler) + ._run() + ) # accept the init response from server assert 1 == stream_calls mock_event_handler.assert_called_once() mock_http_handler.assert_not_called() + mock_bucket_notification_handler.assert_not_called() async def test_trigger_sync_http_handler(self): mock_http_handler = Mock() mock_event_handler = Mock() + mock_bucket_notification_handler = Mock() mock_grpc_channel = Mock() mock_async_channel_init = Mock() mock_async_chan = MockAsyncChannel() @@ -211,16 +230,24 @@ async def mock_stream(self, request_iterator): with patch("nitric.faas.AsyncChannel", mock_async_channel_init), patch( "nitric.proto.nitric.faas.v1.FaasServiceStub.trigger_stream", mock_stream ), patch("nitric.faas.new_default_channel", mock_grpc_channel): - await FunctionServer(opts=FaasWorkerOptions()).http(mock_http_handler).event(mock_event_handler)._run() + await ( + FunctionServer(opts=FaasWorkerOptions()) + .http(mock_http_handler) + .event(mock_event_handler) + .bucket_notification(mock_bucket_notification_handler) + ._run() + ) # accept the init response from server assert 1 == stream_calls mock_http_handler.assert_called_once() mock_event_handler.assert_not_called() + mock_bucket_notification_handler.assert_not_called() async def test_trigger_async_event_handler(self): mock_http_handler = AsyncMock() mock_event_handler = AsyncMock() + mock_bucket_notification_handler = AsyncMock() mock_grpc_channel = Mock() mock_async_channel_init = Mock() mock_async_chan = MockAsyncChannel() @@ -246,16 +273,24 @@ async def mock_stream(self, request_iterator): with patch("nitric.faas.AsyncChannel", mock_async_channel_init), patch( "nitric.proto.nitric.faas.v1.FaasServiceStub.trigger_stream", mock_stream ), patch("nitric.faas.new_default_channel", mock_grpc_channel): - await FunctionServer(opts=FaasWorkerOptions()).http(mock_http_handler).event(mock_event_handler)._run() + await ( + FunctionServer(opts=FaasWorkerOptions()) + .http(mock_http_handler) + .event(mock_event_handler) + .bucket_notification(mock_bucket_notification_handler) + ._run() + ) # accept the init response from server assert 1 == stream_calls mock_event_handler.assert_called_once() mock_http_handler.assert_not_called() + mock_bucket_notification_handler.assert_not_called() async def test_trigger_async_http_handler(self): mock_http_handler = AsyncMock() mock_event_handler = AsyncMock() + mock_bucket_notification_handler = AsyncMock() mock_grpc_channel = Mock() mock_async_channel_init = Mock() mock_async_chan = MockAsyncChannel() @@ -281,16 +316,24 @@ async def mock_stream(self, request_iterator): with patch("nitric.faas.AsyncChannel", mock_async_channel_init), patch( "nitric.proto.nitric.faas.v1.FaasServiceStub.trigger_stream", mock_stream ), patch("nitric.faas.new_default_channel", mock_grpc_channel): - await FunctionServer(opts=FaasWorkerOptions()).http(mock_http_handler).event(mock_event_handler)._run() + await ( + FunctionServer(opts=FaasWorkerOptions()) + .http(mock_http_handler) + .event(mock_event_handler) + .bucket_notification(mock_bucket_notification_handler) + ._run() + ) # accept the init response from server assert 1 == stream_calls mock_http_handler.assert_called_once() mock_event_handler.assert_not_called() + mock_bucket_notification_handler.assert_not_called() async def test_failing_async_http_handler(self): mock_http_handler = AsyncMock() mock_event_handler = AsyncMock() + mock_bucket_notification_handler = AsyncMock() mock_grpc_channel = Mock() mock_async_channel_init = Mock() mock_async_chan = MockAsyncChannel() @@ -316,12 +359,19 @@ async def mock_stream(self, request_iterator): with patch("nitric.faas.AsyncChannel", mock_async_channel_init), patch( "nitric.proto.nitric.faas.v1.FaasServiceStub.trigger_stream", mock_stream ), patch("nitric.faas.new_default_channel", mock_grpc_channel): - await FunctionServer(opts=FaasWorkerOptions()).http(mock_http_handler).event(mock_event_handler)._run() + await ( + FunctionServer(opts=FaasWorkerOptions()) + .http(mock_http_handler) + .event(mock_event_handler) + .bucket_notification(mock_bucket_notification_handler) + ._run() + ) # accept the init response from server assert 1 == stream_calls mock_http_handler.assert_called_once() mock_event_handler.assert_not_called() + mock_bucket_notification_handler.assert_not_called() async def test_failing_topic_handler(self): mock_handler = Mock() @@ -438,6 +488,66 @@ async def mock_stream(self, request_iterator): # Response bytes should be unmodified. self.assertEqual(b"some bytes", message.trigger_response.data) + def test_construct_bucket_notification_worker_options_create(self): + opts = BucketNotificationWorkerOptions( + bucket_name="test-bucket", notification_type="write", notification_prefix_filter="test.png" + ) + + assert opts.bucket_name == "test-bucket" + assert opts.notification_type == BucketNotificationType.Created + assert opts.notification_prefix_filter == "test.png" + + def test_construct_bucket_notification_worker_options_delete(self): + opts = BucketNotificationWorkerOptions( + bucket_name="test-bucket", notification_type="delete", notification_prefix_filter="test.png" + ) + + assert opts.bucket_name == "test-bucket" + assert opts.notification_type == BucketNotificationType.Deleted + assert opts.notification_prefix_filter == "test.png" + + def test_construct_bucket_notification_worker_options_error(self): + with pytest.raises(ValueError) as e: + opts = BucketNotificationWorkerOptions( + bucket_name="test-bucket", notification_type="created", notification_prefix_filter="test.png" + ) + + assert str(e) == "Event type created is unsupported" + + def test_construct_file_notification_worker_options_create(self): + mock_bucket = Mock() + mock_bucket.name = "test-bucket" + opts = FileNotificationWorkerOptions( + bucket=mock_bucket, notification_type="write", notification_prefix_filter="test.png" + ) + + assert opts.bucket_name == "test-bucket" + assert opts.bucket_ref == mock_bucket + assert opts.notification_type == BucketNotificationType.Created + assert opts.notification_prefix_filter == "test.png" + + def test_construct_file_notification_worker_options_delete(self): + mock_bucket = Mock() + mock_bucket.name = "test-bucket" + opts = FileNotificationWorkerOptions( + bucket=mock_bucket, notification_type="delete", notification_prefix_filter="test.png" + ) + + assert opts.bucket_name == "test-bucket" + assert opts.bucket_ref == mock_bucket + assert opts.notification_type == BucketNotificationType.Deleted + assert opts.notification_prefix_filter == "test.png" + + def test_construct_file_notification_worker_options_error(self): + mock_bucket = Mock() + mock_bucket.name = "test-bucket" + with pytest.raises(ValueError) as e: + opts = FileNotificationWorkerOptions( + bucket=mock_bucket, notification_type="created", notification_prefix_filter="test.png" + ) + + assert str(e) == "Event type created is unsupported" + # async def test_handler_dict_response(self): # mock_handler = Mock() # mock_handler.return_value = {"key": "value", "num": 123} From e35bd28fb5291a000bb509df7f85ddc0ecacfa34 Mon Sep 17 00:00:00 2001 From: Ryan Cartwright Date: Mon, 15 May 2023 09:12:59 +1000 Subject: [PATCH 2/2] add license headers --- nitric/proto/__init__.py | 18 ------------------ nitric/proto/nitric/deploy/__init__.py | 18 ------------------ nitric/proto/nitric/deploy/v1/__init__.py | 19 ------------------- nitric/proto/nitric/document/__init__.py | 18 ------------------ nitric/proto/nitric/document/v1/__init__.py | 19 ------------------- nitric/proto/nitric/error/__init__.py | 18 ------------------ nitric/proto/nitric/error/v1/__init__.py | 19 ------------------- nitric/proto/nitric/event/__init__.py | 18 ------------------ nitric/proto/nitric/event/v1/__init__.py | 19 ------------------- nitric/proto/nitric/faas/__init__.py | 18 ------------------ nitric/proto/nitric/faas/v1/__init__.py | 19 ------------------- nitric/proto/nitric/queue/__init__.py | 18 ------------------ nitric/proto/nitric/queue/v1/__init__.py | 19 ------------------- nitric/proto/nitric/resource/__init__.py | 18 ------------------ nitric/proto/nitric/resource/v1/__init__.py | 19 ------------------- nitric/proto/nitric/secret/__init__.py | 18 ------------------ nitric/proto/nitric/secret/v1/__init__.py | 19 ------------------- nitric/proto/nitric/storage/__init__.py | 18 ------------------ nitric/proto/nitric/storage/v1/__init__.py | 19 ------------------- nitric/proto/validate/__init__.py | 19 ------------------- 20 files changed, 370 deletions(-) diff --git a/nitric/proto/__init__.py b/nitric/proto/__init__.py index 4eb07ea..e69de29 100644 --- a/nitric/proto/__init__.py +++ b/nitric/proto/__init__.py @@ -1,18 +0,0 @@ -# -# Copyright (c) 2021 Nitric Technologies Pty Ltd. -# -# This file is part of Nitric Python 3 SDK. -# See https://github.com/nitrictech/python-sdk for further info. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# diff --git a/nitric/proto/nitric/deploy/__init__.py b/nitric/proto/nitric/deploy/__init__.py index 4eb07ea..e69de29 100644 --- a/nitric/proto/nitric/deploy/__init__.py +++ b/nitric/proto/nitric/deploy/__init__.py @@ -1,18 +0,0 @@ -# -# Copyright (c) 2021 Nitric Technologies Pty Ltd. -# -# This file is part of Nitric Python 3 SDK. -# See https://github.com/nitrictech/python-sdk for further info. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# diff --git a/nitric/proto/nitric/deploy/v1/__init__.py b/nitric/proto/nitric/deploy/v1/__init__.py index 01dec17..ff30c98 100644 --- a/nitric/proto/nitric/deploy/v1/__init__.py +++ b/nitric/proto/nitric/deploy/v1/__init__.py @@ -1,22 +1,3 @@ -# -# Copyright (c) 2021 Nitric Technologies Pty Ltd. -# -# This file is part of Nitric Python 3 SDK. -# See https://github.com/nitrictech/python-sdk for further info. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - # Generated by the protocol buffer compiler. DO NOT EDIT! # sources: proto/deploy/v1/deploy.proto # plugin: python-betterproto diff --git a/nitric/proto/nitric/document/__init__.py b/nitric/proto/nitric/document/__init__.py index 4eb07ea..e69de29 100644 --- a/nitric/proto/nitric/document/__init__.py +++ b/nitric/proto/nitric/document/__init__.py @@ -1,18 +0,0 @@ -# -# Copyright (c) 2021 Nitric Technologies Pty Ltd. -# -# This file is part of Nitric Python 3 SDK. -# See https://github.com/nitrictech/python-sdk for further info. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# diff --git a/nitric/proto/nitric/document/v1/__init__.py b/nitric/proto/nitric/document/v1/__init__.py index 3b1cf26..0321bc6 100644 --- a/nitric/proto/nitric/document/v1/__init__.py +++ b/nitric/proto/nitric/document/v1/__init__.py @@ -1,22 +1,3 @@ -# -# Copyright (c) 2021 Nitric Technologies Pty Ltd. -# -# This file is part of Nitric Python 3 SDK. -# See https://github.com/nitrictech/python-sdk for further info. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - # Generated by the protocol buffer compiler. DO NOT EDIT! # sources: proto/document/v1/document.proto # plugin: python-betterproto diff --git a/nitric/proto/nitric/error/__init__.py b/nitric/proto/nitric/error/__init__.py index 4eb07ea..e69de29 100644 --- a/nitric/proto/nitric/error/__init__.py +++ b/nitric/proto/nitric/error/__init__.py @@ -1,18 +0,0 @@ -# -# Copyright (c) 2021 Nitric Technologies Pty Ltd. -# -# This file is part of Nitric Python 3 SDK. -# See https://github.com/nitrictech/python-sdk for further info. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# diff --git a/nitric/proto/nitric/error/v1/__init__.py b/nitric/proto/nitric/error/v1/__init__.py index 71de5c7..09e6ec8 100644 --- a/nitric/proto/nitric/error/v1/__init__.py +++ b/nitric/proto/nitric/error/v1/__init__.py @@ -1,22 +1,3 @@ -# -# Copyright (c) 2021 Nitric Technologies Pty Ltd. -# -# This file is part of Nitric Python 3 SDK. -# See https://github.com/nitrictech/python-sdk for further info. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - # Generated by the protocol buffer compiler. DO NOT EDIT! # sources: proto/error/v1/error.proto # plugin: python-betterproto diff --git a/nitric/proto/nitric/event/__init__.py b/nitric/proto/nitric/event/__init__.py index 4eb07ea..e69de29 100644 --- a/nitric/proto/nitric/event/__init__.py +++ b/nitric/proto/nitric/event/__init__.py @@ -1,18 +0,0 @@ -# -# Copyright (c) 2021 Nitric Technologies Pty Ltd. -# -# This file is part of Nitric Python 3 SDK. -# See https://github.com/nitrictech/python-sdk for further info. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# diff --git a/nitric/proto/nitric/event/v1/__init__.py b/nitric/proto/nitric/event/v1/__init__.py index af1b5bb..cd16894 100644 --- a/nitric/proto/nitric/event/v1/__init__.py +++ b/nitric/proto/nitric/event/v1/__init__.py @@ -1,22 +1,3 @@ -# -# Copyright (c) 2021 Nitric Technologies Pty Ltd. -# -# This file is part of Nitric Python 3 SDK. -# See https://github.com/nitrictech/python-sdk for further info. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - # Generated by the protocol buffer compiler. DO NOT EDIT! # sources: proto/event/v1/event.proto # plugin: python-betterproto diff --git a/nitric/proto/nitric/faas/__init__.py b/nitric/proto/nitric/faas/__init__.py index 4eb07ea..e69de29 100644 --- a/nitric/proto/nitric/faas/__init__.py +++ b/nitric/proto/nitric/faas/__init__.py @@ -1,18 +0,0 @@ -# -# Copyright (c) 2021 Nitric Technologies Pty Ltd. -# -# This file is part of Nitric Python 3 SDK. -# See https://github.com/nitrictech/python-sdk for further info. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# diff --git a/nitric/proto/nitric/faas/v1/__init__.py b/nitric/proto/nitric/faas/v1/__init__.py index 2f87728..e5ad6a9 100644 --- a/nitric/proto/nitric/faas/v1/__init__.py +++ b/nitric/proto/nitric/faas/v1/__init__.py @@ -1,22 +1,3 @@ -# -# Copyright (c) 2021 Nitric Technologies Pty Ltd. -# -# This file is part of Nitric Python 3 SDK. -# See https://github.com/nitrictech/python-sdk for further info. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - # Generated by the protocol buffer compiler. DO NOT EDIT! # sources: proto/faas/v1/faas.proto # plugin: python-betterproto diff --git a/nitric/proto/nitric/queue/__init__.py b/nitric/proto/nitric/queue/__init__.py index 4eb07ea..e69de29 100644 --- a/nitric/proto/nitric/queue/__init__.py +++ b/nitric/proto/nitric/queue/__init__.py @@ -1,18 +0,0 @@ -# -# Copyright (c) 2021 Nitric Technologies Pty Ltd. -# -# This file is part of Nitric Python 3 SDK. -# See https://github.com/nitrictech/python-sdk for further info. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# diff --git a/nitric/proto/nitric/queue/v1/__init__.py b/nitric/proto/nitric/queue/v1/__init__.py index 039d7f9..3ceb3e3 100644 --- a/nitric/proto/nitric/queue/v1/__init__.py +++ b/nitric/proto/nitric/queue/v1/__init__.py @@ -1,22 +1,3 @@ -# -# Copyright (c) 2021 Nitric Technologies Pty Ltd. -# -# This file is part of Nitric Python 3 SDK. -# See https://github.com/nitrictech/python-sdk for further info. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - # Generated by the protocol buffer compiler. DO NOT EDIT! # sources: proto/queue/v1/queue.proto # plugin: python-betterproto diff --git a/nitric/proto/nitric/resource/__init__.py b/nitric/proto/nitric/resource/__init__.py index 4eb07ea..e69de29 100644 --- a/nitric/proto/nitric/resource/__init__.py +++ b/nitric/proto/nitric/resource/__init__.py @@ -1,18 +0,0 @@ -# -# Copyright (c) 2021 Nitric Technologies Pty Ltd. -# -# This file is part of Nitric Python 3 SDK. -# See https://github.com/nitrictech/python-sdk for further info. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# diff --git a/nitric/proto/nitric/resource/v1/__init__.py b/nitric/proto/nitric/resource/v1/__init__.py index 2cc9112..6eae036 100644 --- a/nitric/proto/nitric/resource/v1/__init__.py +++ b/nitric/proto/nitric/resource/v1/__init__.py @@ -1,22 +1,3 @@ -# -# Copyright (c) 2021 Nitric Technologies Pty Ltd. -# -# This file is part of Nitric Python 3 SDK. -# See https://github.com/nitrictech/python-sdk for further info. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - # Generated by the protocol buffer compiler. DO NOT EDIT! # sources: proto/resource/v1/resource.proto # plugin: python-betterproto diff --git a/nitric/proto/nitric/secret/__init__.py b/nitric/proto/nitric/secret/__init__.py index 4eb07ea..e69de29 100644 --- a/nitric/proto/nitric/secret/__init__.py +++ b/nitric/proto/nitric/secret/__init__.py @@ -1,18 +0,0 @@ -# -# Copyright (c) 2021 Nitric Technologies Pty Ltd. -# -# This file is part of Nitric Python 3 SDK. -# See https://github.com/nitrictech/python-sdk for further info. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# diff --git a/nitric/proto/nitric/secret/v1/__init__.py b/nitric/proto/nitric/secret/v1/__init__.py index e5e0553..c369d3d 100644 --- a/nitric/proto/nitric/secret/v1/__init__.py +++ b/nitric/proto/nitric/secret/v1/__init__.py @@ -1,22 +1,3 @@ -# -# Copyright (c) 2021 Nitric Technologies Pty Ltd. -# -# This file is part of Nitric Python 3 SDK. -# See https://github.com/nitrictech/python-sdk for further info. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - # Generated by the protocol buffer compiler. DO NOT EDIT! # sources: proto/secret/v1/secret.proto # plugin: python-betterproto diff --git a/nitric/proto/nitric/storage/__init__.py b/nitric/proto/nitric/storage/__init__.py index 4eb07ea..e69de29 100644 --- a/nitric/proto/nitric/storage/__init__.py +++ b/nitric/proto/nitric/storage/__init__.py @@ -1,18 +0,0 @@ -# -# Copyright (c) 2021 Nitric Technologies Pty Ltd. -# -# This file is part of Nitric Python 3 SDK. -# See https://github.com/nitrictech/python-sdk for further info. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# diff --git a/nitric/proto/nitric/storage/v1/__init__.py b/nitric/proto/nitric/storage/v1/__init__.py index 6d86f0a..6e6130e 100644 --- a/nitric/proto/nitric/storage/v1/__init__.py +++ b/nitric/proto/nitric/storage/v1/__init__.py @@ -1,22 +1,3 @@ -# -# Copyright (c) 2021 Nitric Technologies Pty Ltd. -# -# This file is part of Nitric Python 3 SDK. -# See https://github.com/nitrictech/python-sdk for further info. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - # Generated by the protocol buffer compiler. DO NOT EDIT! # sources: proto/storage/v1/storage.proto # plugin: python-betterproto diff --git a/nitric/proto/validate/__init__.py b/nitric/proto/validate/__init__.py index 685cbab..8b5dd42 100644 --- a/nitric/proto/validate/__init__.py +++ b/nitric/proto/validate/__init__.py @@ -1,22 +1,3 @@ -# -# Copyright (c) 2021 Nitric Technologies Pty Ltd. -# -# This file is part of Nitric Python 3 SDK. -# See https://github.com/nitrictech/python-sdk for further info. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - # Generated by the protocol buffer compiler. DO NOT EDIT! # sources: validate/validate.proto # plugin: python-betterproto