Skip to content

Commit ee428b5

Browse files
authored
Release (#94)
2 parents bfe6457 + f6ffdf5 commit ee428b5

File tree

4 files changed

+68
-12
lines changed

4 files changed

+68
-12
lines changed

nitric/api/exception.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,9 +141,19 @@ class UnknownException(NitricServiceException):
141141

142142
pass
143143

144+
144145
class NitricResourceException(Exception):
146+
"""Illegal nitric resource creation."""
147+
145148
pass
146149

150+
151+
class NitricUnavailableException(Exception):
152+
"""Unable to connect to a nitric server."""
153+
154+
pass
155+
156+
147157
def exception_from_grpc_error(error: GRPCError):
148158
"""Translate a gRPC error to a nitric api exception."""
149159
return exception_from_grpc_code(error.status.value, error.message)

nitric/application.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import asyncio
22
from nitric.faas import FunctionServer
3+
from nitric.api.exception import NitricUnavailableException
34

45
# from nitric.resources.base import BaseResource
56
from typing import Dict, List, Type, Any, TypeVar
@@ -28,11 +29,16 @@ def _register_worker(cls, srv: FunctionServer):
2829

2930
@classmethod
3031
def _create_resource(cls, resource: Type[BT], name: str, *args, **kwargs) -> BT:
31-
resource_type = resource.__name__.lower()
32-
if cls._cache.get(resource_type).get(name) is None:
33-
cls._cache[resource_type][name] = resource.make(name, *args, **kwargs)
32+
try:
33+
resource_type = resource.__name__.lower()
34+
if cls._cache.get(resource_type).get(name) is None:
35+
cls._cache[resource_type][name] = resource.make(name, *args, **kwargs)
3436

35-
return cls._cache[resource_type][name]
37+
return cls._cache[resource_type][name]
38+
except ConnectionRefusedError:
39+
raise NitricUnavailableException(
40+
'Unable to connect to a nitric server! If you\'re running locally make sure to run "nitric start"'
41+
)
3642

3743
@classmethod
3844
def run(cls):
@@ -50,3 +56,7 @@ def run(cls):
5056
loop.run_until_complete(asyncio.gather(*[wkr.start() for wkr in cls._workers]))
5157
except KeyboardInterrupt:
5258
print("\nexiting")
59+
except ConnectionRefusedError:
60+
raise NitricUnavailableException(
61+
'Unable to connect to a nitric server! If you\'re running locally make sure to run "nitric start"'
62+
)

nitric/resources/__init__.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
#
1919
"""Nitric Python SDK API Documentation. See: https://nitric.io/docs?lang=python for full framework documentation."""
2020

21-
from nitric.resources.apis import Api, api, MethodOptions, ApiOptions
21+
from nitric.resources.apis import Api, api, MethodOptions, ApiOptions, ApiDetails, JwtSecurityDefinition
2222
from nitric.resources.buckets import Bucket, bucket
2323
from nitric.resources.collections import Collection, collection
2424
from nitric.resources.queues import Queue, queue
@@ -30,6 +30,8 @@
3030
"api",
3131
"Api",
3232
"ApiOptions",
33+
"ApiDetails",
34+
"JwtSecurityDefinition",
3335
"MethodOptions",
3436
"bucket",
3537
"Bucket",
@@ -42,5 +44,5 @@
4244
"secret",
4345
"Secret",
4446
"topic",
45-
"Topic"
47+
"Topic",
4648
]

nitric/resources/apis.py

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#
1919
from __future__ import annotations
2020
from typing import List, Union
21+
from dataclasses import dataclass
2122
from nitric.faas import ApiWorkerOptions, FunctionServer, HttpMiddleware, Middleware, MethodOptions, HttpMethod
2223
from nitric.application import Nitric
2324
from nitric.resources.base import BaseResource
@@ -29,11 +30,27 @@
2930
ApiSecurityDefinition,
3031
ApiSecurityDefinitionJwt,
3132
ResourceDeclareRequest,
33+
ResourceDetailsRequest,
3234
)
3335
from grpclib import GRPCError
3436
from nitric.api.exception import exception_from_grpc_error
3537

3638

39+
@dataclass
40+
class ApiDetails:
41+
"""Represents the APIs deployment details."""
42+
43+
# the identifier of the resource
44+
id: str
45+
# The provider this resource is deployed with (e.g. aws)
46+
provider: str
47+
# The service this resource is deployed on (e.g. ApiGateway)
48+
service: str
49+
# The url of the API
50+
url: str
51+
52+
53+
@dataclass
3754
class JwtSecurityDefinition:
3855
"""Represents the JWT security definition for an API."""
3956

@@ -58,7 +75,7 @@ class ApiOptions:
5875
def __init__(
5976
self,
6077
path: str = "",
61-
middleware: List[Middleware] = None,
78+
middleware: List[Middleware] = [],
6279
security_definitions: dict[str, SecurityDefinition] = None,
6380
security: dict[str, List[str]] = None,
6481
):
@@ -74,7 +91,7 @@ class RouteOptions:
7491

7592
middleware: Union[None, List[Middleware]]
7693

77-
def __init__(self, middleware: List[Middleware] = None):
94+
def __init__(self, middleware: List[Middleware] = []):
7895
"""Construct a new route options object."""
7996
self.middleware = middleware
8097

@@ -118,7 +135,7 @@ def __init__(self, name: str, opts: ApiOptions = None):
118135
opts = ApiOptions()
119136

120137
self.name = name
121-
self.middleware = opts.middleware
138+
self.middleware = opts.middleware if opts.middleware is not None else []
122139
self.path = opts.path
123140
self.routes = []
124141
self.security_definitions = opts.security_definitions
@@ -246,6 +263,23 @@ def decorator(function: HttpMiddleware):
246263

247264
return decorator
248265

266+
async def _details(self) -> ApiDetails:
267+
"""Get the API deployment details."""
268+
try:
269+
res = await self._resources_stub.details(
270+
resource_details_request=ResourceDetailsRequest(
271+
resource=_to_resource(self),
272+
)
273+
)
274+
return ApiDetails(res.id, res.provider, res.service, res.api.url)
275+
except GRPCError as grpc_err:
276+
raise exception_from_grpc_error(grpc_err)
277+
278+
async def URL(self) -> str:
279+
"""Get the APIs live URL."""
280+
details = await self._details()
281+
return details.url
282+
249283

250284
class Route:
251285
"""An HTTP route."""
@@ -257,8 +291,8 @@ class Route:
257291
def __init__(self, api: Api, path: str, opts: RouteOptions):
258292
"""Define a route to be handled by the provided API."""
259293
self.api = api
260-
self.path = path
261-
self.middleware = opts.middleware
294+
self.path = api.path.join(path)
295+
self.middleware = opts.middleware if opts.middleware is not None else []
262296

263297
def method(self, methods: List[HttpMethod], *middleware: HttpMiddleware, opts: MethodOptions = None):
264298
"""Register middleware for multiple HTTP Methods."""
@@ -304,7 +338,7 @@ def __init__(
304338
self.route = route
305339
self.methods = methods
306340
self.server = FunctionServer(ApiWorkerOptions(route.api.name, route.path, methods, opts))
307-
self.server.http(*middleware)
341+
self.server.http(*route.api.middleware, *route.middleware, *middleware)
308342

309343
def start(self):
310344
"""Start the server which will respond to incoming requests."""

0 commit comments

Comments
 (0)