Skip to content

add linear webhook example #501

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

Merged
merged 12 commits into from
Feb 19, 2025
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
3 changes: 3 additions & 0 deletions codegen-examples/examples/linear_webhooks/.env.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
LINEAR_ACCESS_TOKEN="..."
LINEAR_SIGNING_SECRET="..."
LINEAR_TEAM_ID="..."
6 changes: 6 additions & 0 deletions codegen-examples/examples/linear_webhooks/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[project]
name = "linear-webhook-example"
version = "0.1.0"
description = "Linear webhook example for Codegen"
requires-python = ">=3.13"
dependencies = ["codegen>=0.22.2", "modal>=0.73.25"]
2,395 changes: 2,395 additions & 0 deletions codegen-examples/examples/linear_webhooks/uv.lock

Large diffs are not rendered by default.

28 changes: 28 additions & 0 deletions codegen-examples/examples/linear_webhooks/webhooks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import modal.running_app
from codegen.extensions.events.app import CodegenApp
import modal

image = modal.Image.debian_slim(python_version="3.13").apt_install("git").pip_install("fastapi[standard]", "codegen>=v0.22.2")
app = CodegenApp(name="test-linear", modal_api_key="", image=image)

# Here is an example implementation of setting up an endpoint for receiving webhook events from Linear.
# The @app.linear.event() decorator takes care of subscribing to the webhook and also unsubscribing when the deployment spun
# Load environment variables from .env file


@app.cls(secrets=[modal.Secret.from_dotenv()], keep_warm=1)
class LinearEventHandlers:
@modal.enter()
def enter(self):
app.linear.subscribe_all_handlers()

@modal.exit()
def exit(self):
app.linear.unsubscribe_all_handlers()

@modal.web_endpoint(method="POST")
@app.linear.event("Issue")
def test(self, data: dict):
# handle webhook event
# data is the payload of the webhook event
print(data)
2 changes: 1 addition & 1 deletion src/codegen/extensions/clients/linear.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ def unregister_webhook(self, webhook_id: str):
response = requests.post(self.api_endpoint, headers=self.api_headers, json={"query": mutation, "variables": variables})
return response.json()

def register_webhook(self, webhook_url: str, team_id: str, secret: str, enabled: bool, resource_types: list[str]):
def register_webhook(self, webhook_url: str, team_id: str, secret: str, enabled: bool, resource_types: list[str]) -> str | None:
mutation = """
mutation createWebhook($input: WebhookCreateInput!) {
webhookCreate(input: $input) {
Expand Down
22 changes: 12 additions & 10 deletions src/codegen/extensions/events/linear.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
class RegisteredWebhookHandler(BaseModel):
webhook_id: str | None = None
handler_func: Callable
event_name: str


class Linear(EventHandlerManagerProtocol):
Expand All @@ -23,15 +24,22 @@
self.access_token = os.environ["LINEAR_ACCESS_TOKEN"] # move to extensions config.
self.signing_secret = os.environ["LINEAR_SIGNING_SECRET"]
self.linear_team_id = os.environ["LINEAR_TEAM_ID"]
self.registered_handlers = {}

Check failure on line 27 in src/codegen/extensions/events/linear.py

View workflow job for this annotation

GitHub Actions / mypy

error: Need type annotation for "registered_handlers" (hint: "registered_handlers: dict[<type>, <type>] = ...") [var-annotated]
self._webhook_url = None

def subscribe_handler_to_webhook(self, web_url: str, event_name: str):
def subscribe_handler_to_webhook(self, handler: RegisteredWebhookHandler):

Check failure on line 30 in src/codegen/extensions/events/linear.py

View workflow job for this annotation

GitHub Actions / mypy

error: Signature of "subscribe_handler_to_webhook" incompatible with supertype "EventHandlerManagerProtocol" [override]
client = LinearClient(access_token=self.access_token)

result = client.register_webhook(team_id=self.linear_team_id, webhook_url=web_url, enabled=True, resource_types=[event_name], secret=self.signing_secret)
web_url = modal.Function.from_name(app_name=self.app.name, name=handler.handler_func.__qualname__).web_url

Check failure on line 32 in src/codegen/extensions/events/linear.py

View workflow job for this annotation

GitHub Actions / mypy

error: Argument "app_name" to "from_name" of "Function" has incompatible type "str | None"; expected "str" [arg-type]
result = client.register_webhook(team_id=self.linear_team_id, webhook_url=web_url, enabled=True, resource_types=[handler.event_name], secret=self.signing_secret)

Check failure on line 33 in src/codegen/extensions/events/linear.py

View workflow job for this annotation

GitHub Actions / mypy

error: Argument "webhook_url" to "register_webhook" of "LinearClient" has incompatible type "str | None"; expected "str" [arg-type]
return result

def subscribe_all_handlers(self):
for handler_key in self.registered_handlers:
handler = self.registered_handlers[handler_key]
result = self.subscribe_handler_to_webhook(handler=self.registered_handlers[handler_key])
handler.webhook_id = result

def unsubscribe_handler_to_webhook(self, registered_handler: RegisteredWebhookHandler):

Check failure on line 42 in src/codegen/extensions/events/linear.py

View workflow job for this annotation

GitHub Actions / mypy

error: Signature of "unsubscribe_handler_to_webhook" incompatible with supertype "EventHandlerManagerProtocol" [override]
webhook_id = registered_handler.webhook_id

client = LinearClient(access_token=self.access_token)
Expand Down Expand Up @@ -59,13 +67,7 @@
# Register the handler with the app's registry.
modal_ready_func = func
func_name = func.__qualname__
app_name = self.app.name
web_url = modal.Function.from_name(app_name=app_name, name=func_name).web_url

self.registered_handlers[func_name] = RegisteredWebhookHandler(handler_func=modal_ready_func)

webhook_id = self.subscribe_handler_to_webhook(web_url=web_url, event_name=event_name)
self.registered_handlers[func_name].webhook_id = webhook_id
self.registered_handlers[func_name] = RegisteredWebhookHandler(handler_func=modal_ready_func, event_name=event_name)

@functools.wraps(func)
def wrapper(*args, **kwargs):
Expand Down
Loading