Skip to content

Commit f065e0b

Browse files
authored
Improved cross-language homogeneity (#34)
Use an explicit "handle" method on Function instances, as this is the most similar method signature approach between different language; a primary goal of the project. This is in contrast to a more strict adherence to the ASGI spec which would see the Function object itself be callable. See PR for further exploration of this choice.
1 parent eb41ca5 commit f065e0b

File tree

4 files changed

+22
-11
lines changed

4 files changed

+22
-11
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
1010

1111
### Added
1212
### Changed
13+
14+
- Simplified instanced functions by expecting a named 'handle' method.
15+
1316
### Deprecated
1417
### Removed
1518
### Fixed
@@ -19,13 +22,15 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
1922
## [0.2.0] - 2024-11-06
2023

2124
### Added
25+
2226
- optional message returns from lifeycle methods "alive" and "ready"
2327
- expanded development and release documentation
2428

2529

2630
## [0.1.0] - 2024-10-28
2731

2832
### Added
33+
2934
- Initial Implementation of the Python HTTP Functions Middleware
3035

3136

cmd/fhttp/main.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,11 @@ async def handle(scope, receive, send):
3838

3939
# Example instanced handler
4040
# This is the default expected by this test.
41-
# The class can be named anything. See "new" below.
41+
# The class can be named anything, but there must be a constructor named "new"
42+
# which returns an object with an async method "handle" conforming to the ASGI
43+
# callable's method signature.
4244
class MyFunction:
43-
async def __call__(self, scope, receive, send):
45+
async def handle(self, scope, receive, send):
4446
logging.info("OK")
4547

4648
await send({
@@ -68,7 +70,8 @@ def stop(self):
6870

6971
# Function instance constructor
7072
# expected to be named exactly "new"
71-
# Must return a callable which conforms to the ASGI spec.
73+
# Must return a object which exposes a method "handle" which conforms to the
74+
# ASGI callable spec.
7275
def new():
7376
""" new is the factory function (or constructor) which will create
7477
a new function instance when invoked. This must be named "new", and the

src/func_python/http.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
logging.basicConfig(level=DEFAULT_LOG_LEVEL)
1313

14+
1415
def serve(f):
1516
"""serve a function f by wrapping it in an ASGI web application
1617
and starting. The function can be either a constructor for a functon
@@ -28,7 +29,7 @@ def serve(f):
2829
raise
2930
else:
3031
raise ValueError("function must be either be a constructor 'new' or a "
31-
"handler 'handle'.")
32+
"handler function 'handle'.")
3233

3334

3435
class DefaultFunction:
@@ -38,7 +39,7 @@ class DefaultFunction:
3839
def __init__(self, handler):
3940
self.handle = handler
4041

41-
async def __call__(self, scope, receive, send):
42+
async def handle(self, scope, receive, send):
4243
# delegate to the handler implementation provided during construction.
4344
await self.handle(scope, receive, send)
4445

@@ -47,6 +48,9 @@ class ASGIApplication():
4748
def __init__(self, f):
4849
self.f = f
4950
self.stop_event = asyncio.Event()
51+
if hasattr(self.f, "handle") is not True:
52+
raise AttributeError( "Function must implement a 'handle' method.")
53+
5054
# Inform the user via logs that defaults will be used for health
5155
# endpoints if no matchin methods were provided.
5256
if hasattr(self.f, "alive") is not True:
@@ -125,10 +129,7 @@ async def __call__(self, scope, receive, send):
125129
elif scope['path'] == '/health/readiness':
126130
await self.handle_readiness(scope, receive, send)
127131
else:
128-
if callable(self.f):
129-
await self.f(scope, receive, send)
130-
else:
131-
raise Exception("function does not implement handle")
132+
await self.f.handle(scope, receive, send)
132133
except Exception as e:
133134
await send_exception(send, 500, f"Error: {e}")
134135

tests/test_http.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,18 @@
1010

1111
# Set a dynamic test URL using an environment variable
1212
os.environ["LISTEN_ADDRESS"] = os.getenv("LISTEN_ADDRESS", "127.0.0.1:8081")
13+
1314
# Retrieve the LISTEN_ADDRESS for use in the tests
1415
LISTEN_ADDRESS = os.getenv("LISTEN_ADDRESS")
1516

17+
1618
def test_static():
1719
"""
1820
ensures that a user function developed using the default "static"
1921
style (method signature) is served by the middleware.
2022
"""
2123

22-
# Functoin
24+
# Function
2325
# An example minimal "static" user function which will be
2426
# exposed on the network as an ASGI service by the middleware.
2527
async def handle(scope, receive, send):
@@ -81,7 +83,7 @@ def test_instanced():
8183
# An example standard "instanced" function (user's Function) which is
8284
# exposed on the network as an ASGI service by the middleware.
8385
class MyFunction:
84-
async def __call__(self, scope, receive, send):
86+
async def handle(self, scope, receive, send):
8587
await send({
8688
'type': 'http.response.start',
8789
'status': 200,

0 commit comments

Comments
 (0)