-
Notifications
You must be signed in to change notification settings - Fork 16
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add FastAPI support #287
Add FastAPI support #287
Conversation
682f1e9
to
6558881
Compare
1f11b75
to
5811e44
Compare
5eb86d8
to
b88a040
Compare
Having the Django and Flask apps both use the module name "app" makes the tests order-dependent. If the Django "app" is loaded before the Flask tests, the latter fail because they try to import "app.main", which doesn't exist Maybe there's some other way to avoid this collision by unloading "app" and still have all the tests pass, but I couldn't find it. Just renaming them seemed simplest.
importlib.metadata was added in 3.8. Now that we no longer support 3.7, we can use it directly, rather than using the backported implementation. (We still need to use the backported version of importlib_resources until we drop 3.8.)
84cf3a7
to
a7fd460
Compare
@@ -145,7 +147,7 @@ def after_request_main(self, rec, status, headers, start, call_event_id) -> None | |||
parent_id=call_event_id, | |||
elapsed=duration, | |||
status_code=status, | |||
headers=dict(headers.items()), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The keys in a headers collection need to be matched in a case-insensitive manner, so we can't just create a dict
from them. This also isn't necessary, because the various web frameworks represent them with with a type that quacks enough like a dict
for our purposes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there any place where we're actually matching them outside of tests? I think it might be a better idea to normalize the header names in appmaps instead.
aac19d7
to
27dec47
Compare
_appmap/test/test_django.py
Outdated
**server_env, | ||
} | ||
|
||
xprocess.ensure("myserver", Starter) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would be better to use django_test_server
or something more meaningful here, even if it's unlikely to clash. (Same for flask.)
_appmap/test/test_django.py
Outdated
"PYTHONUNBUFFERED": "1", | ||
"APPMAP_OUTPUT_DIR": "/tmp", | ||
**server_env, | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be a good idea to set terminate_on_interrupt = True
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I noticed that too, and made that change here: https://github.com/getappmap/appmap-python/pull/289/files#diff-3ea36cadab578779c37a4b328bf128e35b5b45e54aee717aed08f96748114bd5R92 .
I'll bring it over.
_appmap/test/test_flask.py
Outdated
f"cd {Path(__file__).parent / 'data'/ 'flask'};" | ||
+ f" {sys.executable} -m flask run" | ||
+ f" -p {port}", | ||
] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of cd-ing in a bash script, I believe you can instead pass cwd=
in popen_kwargs
for clarity and portability. (Same for django.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using cwd=
is currently broken, as I mentioned here: 27dec47#diff-3ea36cadab578779c37a4b328bf128e35b5b45e54aee717aed08f96748114bd5R92 .
Other versions of test_flask.py and test_django.py had this comment, too, but I guess it got deleted along the way. I'll put it back.
_appmap/test/test_flask.py
Outdated
return True | ||
except ConnectionRefusedError: | ||
pass | ||
return False |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Those Django and Flask servers are awfully similar, consider whether it's possible (and worthwhile) to DRY them up.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed. Let me see if it'd be a quick change.
@@ -145,7 +147,7 @@ def after_request_main(self, rec, status, headers, start, call_event_id) -> None | |||
parent_id=call_event_id, | |||
elapsed=duration, | |||
status_code=status, | |||
headers=dict(headers.items()), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there any place where we're actually matching them outside of tests? I think it might be a better idea to normalize the header names in appmaps instead.
@@ -82,6 +86,7 @@ def normalize(dct): | |||
if len(dct["headers"]) == 0: | |||
del dct["headers"] | |||
if "http_server_request" in dct: | |||
dct["http_server_request"].pop("headers", None) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd rather we kept checking for the headers. I assume you changed it because FastAPI formats them differently. If you normalize the capitalization before generating the AppMap like I suggested, you should be able to bring it back.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I updated the tests to check that the http_server_request
properties that depend on header values get set correctly. I can update this so it verifies that headers
is present.
Is the combination of those two checks sufficient?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, though maybe it could use a comment.
@@ -142,6 +142,7 @@ def before_request_main(self, rec, req: Any) -> Tuple[float, int]: | |||
raise NotImplementedError | |||
|
|||
def after_request_main(self, rec, status, headers, start, call_event_id) -> None: | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this intended?
Remove our bespoke test server management, use pytest-xprocess instead. Ensures that pytest doesn't hang if a test fails, and that the tests run correctly in VS Code (maybe also on Windows, still to be determined). Also fixes a couple of issues identified by pylint.
6af4508
to
1ac9865
Compare
Add support for the FastAPI framework. Load it automatically when uvicorn is run with a FastAPI app.
Create a log file by default, messages at INFO and above to it. Provide the env var APPMAP_DISABLE_LOG_FILE to instead log (at WARNING, by default) to stderr.
1ac9865
to
3da004f
Compare
Add an integration with FastAPI. Enable it automatically when a FastAPI app is started using
uvicorn
.The changes to instrumentation that are included here are necessary to capture an app's functions that are decorated with FastAPI's routing decorators, e.g.
Also, create a log file by default, instead of writing to stderr. (This behavior has proven very useful in debugging Java agent problems.)
Fixes #251.