Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -149,3 +149,5 @@ cython_debug/
.vscode/

contracts/

testproj/
15 changes: 15 additions & 0 deletions nitric/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
class Nitric:
"""Represents a nitric app."""

_has_run = False

_workers: List[FunctionServer] = []
_cache: Dict[str, Dict[str, Any]] = {
"api": {},
Expand Down Expand Up @@ -61,13 +63,26 @@ def _create_resource(cls, resource: Type[BT], name: str, *args: Any, **kwargs: A
"The nitric server may not be running or the host/port is inaccessible"
) from cre

@classmethod
def has_run(cls) -> bool:
"""
Check if the Nitric application has been started.

Returns:
bool: True if the Nitric application has been started, False otherwise.
"""
return cls._has_run

@classmethod
def run(cls) -> None:
"""
Start the nitric application.

This will execute in an existing event loop if there is one, otherwise it will attempt to create its own.
"""
if cls._has_run:
print("The Nitric application has already been started, Nitric.run() should only be called once.")
cls._has_run = True
try:
try:
loop = asyncio.get_running_loop()
Expand Down
52 changes: 52 additions & 0 deletions nitric/channel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import atexit
import re
from urllib.parse import urlparse
from grpclib.client import Channel

from nitric.application import Nitric
from nitric.config import settings
from nitric.exception import NitricNotRunningException


def format_url(url: str):
"""Add the default http scheme prefix to urls without one."""
if not re.match("^((?:http|ftp|https):)?//", url.lower()):
return "http://{0}".format(url)
return url


class ChannelManager:
"""A singleton class to manage the gRPC channel."""

channel = None

@classmethod
def get_channel(cls) -> Channel:
"""Return the channel instance."""

if cls.channel is None:
cls._create_channel()
return cls.channel # type: ignore

@classmethod
def _create_channel(cls):
"""Create a new channel instance."""

channel_url = urlparse(format_url(settings.SERVICE_ADDRESS))
cls.channel = Channel(host=channel_url.hostname, port=channel_url.port)
atexit.register(cls._close_channel)

@classmethod
def _close_channel(cls):
"""Close the channel instance."""

if cls.channel is not None:
cls.channel.close()
cls.channel = None

# If the program exits without calling Nitric.run(), it may have been a mistake.
if not Nitric.has_run():
print(
"WARNING: The Nitric application was not started. "
"If you intended to start the application, call Nitric.run() before exiting."
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"before exiting" reads a bit strange here, it technically makes sense. Not sure if its better phrasing but maybe something like "at the end of your application?".

)
7 changes: 7 additions & 0 deletions nitric/exception.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,13 @@ def __init__(self, message: str):
super().__init__("Unable to connect to nitric server." + (" " + message if message else ""))


class NitricNotRunningException(Exception):
"""The Nitric application wasn't started before the program exited."""

def __init__(self):
super().__init__("The Nitric application was not started, call Nitric.run() to start the application.")


def exception_from_grpc_error(error: GRPCError):
"""Translate a gRPC error to a nitric api exception."""
return exception_from_grpc_code(error.status.value, error.message or "")
Expand Down
4 changes: 2 additions & 2 deletions nitric/resources/apis.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
ResourceType,
)
from nitric.resources.resource import Resource as BaseResource
from nitric.utils import new_default_channel
from nitric.channel import ChannelManager


@dataclass
Expand Down Expand Up @@ -479,7 +479,7 @@ async def _route_request_iterator(self):

async def start(self) -> None:
"""Register this API route handler and handle http requests."""
channel = new_default_channel()
channel = ChannelManager.get_channel()
server = ApiStub(channel=channel)

# Attach security rules for this route
Expand Down
6 changes: 3 additions & 3 deletions nitric/resources/buckets.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
StorageWriteRequest,
)
from nitric.resources.resource import SecureResource
from nitric.utils import new_default_channel
from nitric.channel import ChannelManager


class BucketNotifyRequest:
Expand Down Expand Up @@ -142,7 +142,7 @@ class BucketRef(object):

def __init__(self, name: str):
"""Construct a Nitric Storage Client."""
self._channel: Union[Channel, None] = new_default_channel()
self._channel: Union[Channel, None] = ChannelManager.get_channel()
self._storage_stub = StorageStub(channel=self._channel)
self.name = name

Expand Down Expand Up @@ -428,7 +428,7 @@ async def _listener_request_iterator(self):

async def start(self) -> None:
"""Register this bucket listener and listen for events."""
channel = new_default_channel()
channel = ChannelManager.get_channel()
server = StorageListenerStub(channel=channel)

try:
Expand Down
5 changes: 3 additions & 2 deletions nitric/resources/kv.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@
ResourceType,
)
from nitric.resources.resource import SecureResource
from nitric.utils import dict_from_struct, new_default_channel, struct_from_dict
from nitric.utils import dict_from_struct, struct_from_dict
from nitric.channel import ChannelManager


class KeyValueStoreRef:
Expand All @@ -54,7 +55,7 @@ class KeyValueStoreRef:

def __init__(self, name: str):
"""Construct a reference to a deployed key value store."""
self._channel: Channel = new_default_channel()
self._channel: Channel = ChannelManager.get_channel()
self._kv_stub = KvStoreStub(channel=self._channel)
self.name = name

Expand Down
5 changes: 3 additions & 2 deletions nitric/resources/queues.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@
from nitric.proto.queues.v1 import QueuesStub as QueueServiceStub
from nitric.proto.resources.v1 import Action, ResourceDeclareRequest, ResourceIdentifier, ResourceType
from nitric.resources.resource import SecureResource
from nitric.utils import dict_from_struct, new_default_channel, struct_from_dict
from nitric.utils import dict_from_struct, struct_from_dict
from nitric.channel import ChannelManager


@dataclass(frozen=True, order=True)
Expand Down Expand Up @@ -91,7 +92,7 @@ class QueueRef(object):

def __init__(self, name: str) -> None:
"""Construct a Nitric Queue Client."""
self._channel: Channel = new_default_channel()
self._channel: Channel = ChannelManager.get_channel()
self._queue_stub = QueueServiceStub(channel=self._channel)
self.name = name

Expand Down
7 changes: 2 additions & 5 deletions nitric/resources/resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
ResourcesStub,
ResourceType,
)
from nitric.utils import new_default_channel
from nitric.channel import ChannelManager

T = TypeVar("T", bound="Resource")

Expand All @@ -48,7 +48,7 @@ def __init__(self, name: str):
"""Construct a new resource."""
self.name = name
self._reg: Optional[Task[Any]] = None # type: ignore
self._channel = new_default_channel()
self._channel = ChannelManager.get_channel()
self._resources_stub = ResourcesStub(channel=self._channel)

@abstractmethod
Expand Down Expand Up @@ -85,9 +85,6 @@ def _perms_to_actions(self, *args: Any) -> List[Action]:
pass

async def _register_policy_async(self, *args: str) -> None:
# if self._reg is not None:
# await asyncio.wait({self._reg})

policy = PolicyResource(
principals=[ResourceIdentifier(type=ResourceType.Service)],
actions=self._perms_to_actions(*args),
Expand Down
4 changes: 2 additions & 2 deletions nitric/resources/schedules.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
ScheduleEvery,
SchedulesStub,
)
from nitric.utils import new_default_channel
from nitric.channel import ChannelManager


class ScheduleServer(FunctionServer):
Expand Down Expand Up @@ -93,7 +93,7 @@ async def _schedule_request_iterator(self):

async def start(self) -> None:
"""Register this schedule and start listening for requests."""
channel = new_default_channel()
channel = ChannelManager.get_channel()
schedules_stub = SchedulesStub(channel=channel)

try:
Expand Down
4 changes: 2 additions & 2 deletions nitric/resources/secrets.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
from nitric.proto.secrets.v1 import SecretAccessRequest, SecretManagerStub, SecretPutRequest
from nitric.proto.secrets.v1 import SecretVersion as VersionMessage
from nitric.resources.resource import SecureResource
from nitric.utils import new_default_channel
from nitric.channel import ChannelManager


class SecretRef:
Expand All @@ -43,7 +43,7 @@ class SecretRef:

def __init__(self, name: str) -> None:
"""Construct a Nitric Storage Client."""
self._channel: Channel = new_default_channel()
self._channel: Channel = ChannelManager.get_channel()
self._secrets_stub = SecretManagerStub(channel=self._channel)
self.name = name

Expand Down
4 changes: 2 additions & 2 deletions nitric/resources/sql.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
ResourceType,
)
from nitric.resources.resource import Resource as BaseResource
from nitric.utils import new_default_channel
from nitric.channel import ChannelManager
from nitric.application import Nitric

from nitric.proto.sql.v1 import SqlStub, SqlConnectionStringRequest
Expand All @@ -50,7 +50,7 @@ def __init__(self, name: str, migrations: Union[str, None] = None):
"""Construct a new SQL Database."""
super().__init__(name)

self._channel: Union[Channel, None] = new_default_channel()
self._channel: Union[Channel, None] = ChannelManager.get_channel()
self._sql_stub = SqlStub(channel=self._channel)
self.name = name
self.migrations = migrations
Expand Down
7 changes: 4 additions & 3 deletions nitric/resources/topics.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@
from nitric.proto.topics.v1 import RegistrationRequest, SubscriberStub
from nitric.proto.topics.v1 import TopicPublishRequest, TopicsStub
from nitric.resources.resource import SecureResource
from nitric.utils import dict_from_struct, new_default_channel, struct_from_dict
from nitric.utils import dict_from_struct, struct_from_dict
from nitric.channel import ChannelManager

TopicPermission = Literal["publish"]

Expand All @@ -51,7 +52,7 @@ class TopicRef:

def __init__(self, name: str) -> None:
"""Construct a reference to a deployed Topic."""
self._channel: Channel = new_default_channel()
self._channel: Channel = ChannelManager.get_channel()
self._topics_stub = TopicsStub(channel=self._channel)
self.name = name

Expand Down Expand Up @@ -161,7 +162,7 @@ async def _message_request_iterator(self):

async def start(self) -> None:
"""Register this subscriber and listen for messages."""
channel = new_default_channel()
channel = ChannelManager.get_channel()
server = SubscriberStub(channel=channel)

try:
Expand Down
6 changes: 3 additions & 3 deletions nitric/resources/websockets.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,15 +51,15 @@
WebsocketStub,
)
from nitric.resources.resource import Resource as BaseResource
from nitric.utils import new_default_channel
from nitric.channel import ChannelManager


class WebsocketRef:
"""A reference to a deployed websocket, used to interact with the websocket at runtime."""

def __init__(self) -> None:
"""Construct a Nitric Websocket Client."""
self._channel: Channel = new_default_channel()
self._channel: Channel = ChannelManager.get_channel()
self._websocket_stub = WebsocketStub(channel=self._channel)

async def send(self, socket: str, connection_id: str, data: bytes):
Expand Down Expand Up @@ -206,7 +206,7 @@ async def _ws_request_iterator(self):

async def start(self) -> None:
"""Register this websocket handler and listen for messages."""
channel = new_default_channel()
channel = ChannelManager.get_channel()
server = WebsocketHandlerStub(channel=channel)

try:
Expand Down
18 changes: 0 additions & 18 deletions nitric/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,29 +16,11 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
import re
from typing import Any, Optional
from urllib.parse import urlparse

from betterproto.lib.google.protobuf import Struct
from google.protobuf.json_format import MessageToDict
from google.protobuf.struct_pb2 import Struct as WorkingStruct
from grpclib.client import Channel

from nitric.config import settings


def format_url(url: str):
"""Add the default http scheme prefix to urls without one."""
if not re.match("^((?:http|ftp|https):)?//", url.lower()):
return "http://{0}".format(url)
return url


def new_default_channel():
"""Create new gRPC channel from settings."""
channel_url = urlparse(format_url(settings.SERVICE_ADDRESS))
return Channel(host=channel_url.hostname, port=channel_url.port)


# These functions convert to/from python dict <-> betterproto.lib.google.protobuf.Struct
Expand Down
30 changes: 0 additions & 30 deletions testproj/functions/hello.py

This file was deleted.