Skip to content

Exception fluency monkeypatching#30

Merged
erikrose merged 4 commits into
mainfrom
exception-fluency-monkeypatching
Feb 3, 2026
Merged

Exception fluency monkeypatching#30
erikrose merged 4 commits into
mainfrom
exception-fluency-monkeypatching

Conversation

@erikrose
Copy link
Copy Markdown
Member

@erikrose erikrose commented Jan 16, 2026

This excises most of the Weird from our exception handling and leaves plenty of maneuvering space for future improvements. The 2 obvious ones are…

  1. Make an affordance for manual polish of individual exceptions, like adding nice getter properties, without clashing with the code generation. I have some ideas on how to do this nicely.
  2. Patch the docstrings of the wrapped routines so they say which new-style exceptions they :raise:. That'll make Sphinx or whatever tell the truth to our users.

But for now, this gets us most of the way there:

  • Based on a union of all the result error types from the WIT, generate more specific exception classes.
  • Generate patches to make componentize-py-generated routines raise those exceptions.
  • Apply those monkeypatches at fastly_compute import time.
  • Move remap_wit_error() to its new home right next to the monkeypatcher which should be its exclusive caller.
  • Teach makefile to run the code generator at the right times.
  • Port requests façade and the exception catch in wsgi.py to the new-style exceptions.

For the moment, I've left the make-tests-passing commit separate just in case you can think of a nicer way of doing it, like perhaps running the patches at sometime other than import time.

@erikrose erikrose force-pushed the exception-fluency-monkeypatching branch 5 times, most recently from fa0df5f to 2b76077 Compare January 29, 2026 20:06
* Based on a union of all the `result` error types from the WIT, generate more specific exception classes.
* Generate patches to make componentize-py-generated routines raise those exceptions.
* Apply those monkeypatches at `fastly_compute` import time.
* Move `remap_wit_error()` to its new home right next to the monkeypatcher which should be its exclusive caller.
* Teach makefile to run the code generator at the right times.
Mostly, don't crash trying to monkeypatch nonexistent hostcalls when imported by the testrunner in the absence of Viceroy. Can't patch hostcalls in the absence of a host!
This is a quick one-to-one port to get things running. Something we should consider before release is to map ErrorWithDetail to one of a set of exceptions corresponding to the detail enumeration if present, otherwise falling back to the error itself. It's not important to the `requests` package since it just turns around and does its own mapping to a `requests`-style exception hierarchy, but it'd be a big ergonomic win for normal callers.
@erikrose erikrose marked this pull request as ready for review January 29, 2026 20:11
I think it's better to emphasize that we're patching at runtime. Are we patching the WIT? Not really; we're patching stuff generated from the WIT. This is less misleading.
Copy link
Copy Markdown
Member

@posborne posborne left a comment

Choose a reason for hiding this comment

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

I'm fine with moving forward with the change and evolving as we go and don't want to get too caught up bikeshedding. I do dislike the monkeypatching but also don't want to stop progress over it.

Comment thread .gitignore
# Generated code
/stubs/
__pycache__
/fastly_compute/exceptions/*
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I would probably be OK and it may make for easier reference to include this generated code in tree given that the inputs probably don't change too frequently and we probably do want to examine changes that occur fairly closely.

When to do regen then becomes a bit different, potentially; perhaps do it manually with some kind of CI check to catch it containing non-cosmetic differences. Not a hill I'd die on, but I do think this could be a case where including the generated code might be worthwhile to aid reference.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

100% agree. I was wishing for that as I developed it, so I'll open a follow-up PR with that change. It's a fairly elegant way of putting this code under test.

sys.path.append(str(Path(__file__).parent.parent / "stubs"))

from componentize_py_types import Err
from wit_world.imports.types import Error_BufferLen, OpenError
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'm running into issues with running tests locally; in general, I think we should avoid importing anything from stubs/wit_world/componentize_py_types in the host python environment as a rule.

We can probably have this test but I think it may be less problematic (though slightly annoying) to move it to exist behind a test shim so it runs within the guest.


# Before anything from the fastly_compute package is used, do our monkeypatching
# to make the WIT-generated code act more Pythonically:
patch()
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'm still uneasy about doing this kind of runtime patching. It introduces magic that is not necessary without any benefit other than reducing the amount of code that needs to be generated slightly.

The alternative I still believe is preferred is to have a generated layer that does the translation; this is similar to what we are patching in at runtime but generated a compile time. Use would look like this:

# Current, use wit_world import that gets magically patched
from wit_world.imports import compute_runtime

# Using generated wrapper (details flexible)
from fastly_compute.wit import compute_runtime

With that in mind, I also think we can move forward with the change as-is and it will not preclude us from changing our approach on this later on. Both would perform a similar transform, sharing most of the same code.

I'll keep thinking on this, but wanted to speak my peace that this feels like a bit of a hack that could cause us some pain and it isn't necessary to achieve what is being achieved here.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Noted! I'm not ordinarily a monkeypatching kind of guy either, but I think in this case the good outweighs the bad. My motivating good is that it effectively clears the undesirable behavior out of the system; no one is going to call a low-level Err-raising routine by accident. That wouldn't ordinarily be worth much worry, except that many of our routines are methods on resources (on classes, in the Python world). Any source-level wrapping would have to carry around a duplicate of each class to house nice-exception-raising methods. Now you've got 2 different Request classes kicking around, throwing different exceptions, returning different kinds of other classes. One slip by a customer or a lib they use (should we be so successful), and you could end up with fairly subtle bugs involving same-named classes, ones which might not be discovered except under (less-tested) error conditions.

A halfway approach might be to throw some kind of warning if wit_world is imported directly. Or stow it under its_a_terrible_idea_to_import_this.wit_world etc.

It might not be a bad idea to convert this into a "2-way door" by importing everything in wit_world through to fastly_compute.wit after all (resurrecting #32). I'm 3/5 in favor of this. What do you think?

" # Tolerate that momentary import for the testrunner before Viceroy, and thus\n"
" # the wit_world, is around.\n"
" def patch():\n"
' print("Faking the run of exception-mapping monkeypatches for test runner.")\n'
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I suppose, add to the list of downsides with __init__.py monkeypatching.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yes, this is a silliness. I'm inclined to port test_nice_exceptions.py to run under Viceroy, which would nix this. It also gets rid of an icky global sys.path twiddle it currently does. I'm uncomfortable about consequences of that leaking out to other test code.


In practice, many types, like variants and the unit type, are represented by
more-specific subclasses, leaving this one to stand in for ones we haven't
needed to specializze for yet.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit: typo.

from .utils import indent, lower_snake, only, shouty_snake, upper_camel


class DocsHaver:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Consider having the abc have a constructor that assigns _me rather than having that be a protocol? I don't care that much, just seems more explicit to have it handled via a chain of super constructors if we're going to have the inheritance hierarchy.

@erikrose
Copy link
Copy Markdown
Member Author

erikrose commented Feb 3, 2026

I'll land this as-is so it unblocks rebasing #34 and #35 and then follow up with PRs that port test_nice_exceptions.py to Viceroy and commit the generated artifacts. Thanks!

@erikrose erikrose merged commit 83038db into main Feb 3, 2026
4 checks passed
@erikrose erikrose deleted the exception-fluency-monkeypatching branch February 3, 2026 16:08
@erikrose erikrose mentioned this pull request Feb 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants