Repository: PrefectHQ/prefect

Issue:
Make it easier to emit a one-off event.
### First check

- [X] I added a descriptive title to this issue.
- [X] I used the GitHub search to find a similar request and didn't find it.
- [X] I searched the Prefect documentation for this feature.

### Prefect Version

2.x

### Describe the current behavior

Currently to emit a single event you have to use the events worker context managers, build the event object manually.

### Describe the proposed behavior

It would be nice if there was just a simple `emit_event` function that handled managing the event worker and initializing the event object for you.

### Example Use

_No response_

### Additional context

_No response_


Discussion:

Resolving patch:
diff --git a/src/prefect/events/__init__.py b/src/prefect/events/__init__.py
--- a/src/prefect/events/__init__.py
+++ b/src/prefect/events/__init__.py
@@ -1,7 +1,9 @@
 from .schemas import Event, Resource, RelatedResource
+from .utilities import emit_event
 
 __all__ = [
     "Event",
     "Resource",
     "RelatedResource",
+    "emit_event",
 ]
diff --git a/src/prefect/events/utilities.py b/src/prefect/events/utilities.py
new file mode 100644
--- /dev/null
+++ b/src/prefect/events/utilities.py
@@ -0,0 +1,51 @@
+from typing import Any, Dict, List, Optional
+from uuid import UUID
+
+from prefect.server.utilities.schemas import DateTimeTZ
+
+from .schemas import Event
+from .worker import get_events_worker
+
+
+def emit_event(
+    event: str,
+    resource: Dict[str, str],
+    occurred: Optional[DateTimeTZ] = None,
+    related: Optional[List[Dict[str, str]]] = None,
+    payload: Optional[Dict[str, Any]] = None,
+    id: Optional[UUID] = None,
+) -> None:
+    """
+    Send an event to Prefect Cloud.
+
+    Args:
+        event: The name of the event that happened.
+        resource: The primary Resource this event concerns.
+        occurred: When the event happened from the sender's perspective.
+                  Defaults to the current datetime.
+        related: A list of additional Resources involved in this event.
+        payload: An open-ended set of data describing what happened.
+        id: The sender-provided identifier for this event. Defaults to a random
+            UUID.
+    """
+    event_kwargs = {
+        "event": event,
+        "resource": resource,
+    }
+
+    if occurred is not None:
+        event_kwargs["occurred"] = occurred
+
+    if related is not None:
+        event_kwargs["related"] = related
+
+    if payload is not None:
+        event_kwargs["payload"] = payload
+
+    if id is not None:
+        event_kwargs["id"] = id
+
+    event_obj = Event(**event_kwargs)
+
+    with get_events_worker() as worker:
+        worker.emit(event_obj)


Test patch:
diff --git a/tests/events/test_events_emit_event.py b/tests/events/test_events_emit_event.py
new file mode 100644
--- /dev/null
+++ b/tests/events/test_events_emit_event.py
@@ -0,0 +1,56 @@
+import time
+from uuid import UUID
+
+from prefect.events import emit_event
+from prefect.events.clients import AssertingEventsClient
+from prefect.events.worker import EventsWorker
+from prefect.server.utilities.schemas import DateTimeTZ
+
+
+def test_emits_simple_event(asserting_events_worker: EventsWorker, reset_worker_events):
+    emit_event(
+        event="vogon.poetry.read",
+        resource={"prefect.resource.id": "vogon.poem.oh-freddled-gruntbuggly"},
+    )
+
+    time.sleep(0.1)
+
+    assert isinstance(asserting_events_worker._client, AssertingEventsClient)
+
+    assert len(asserting_events_worker._client.events) == 1
+    event = asserting_events_worker._client.events[0]
+    assert event.event == "vogon.poetry.read"
+    assert event.resource.id == "vogon.poem.oh-freddled-gruntbuggly"
+
+
+def test_emits_complex_event(
+    asserting_events_worker: EventsWorker, reset_worker_events
+):
+    emit_event(
+        event="vogon.poetry.read",
+        resource={"prefect.resource.id": "vogon.poem.oh-freddled-gruntbuggly"},
+        occurred=DateTimeTZ(2023, 3, 1, 12, 39, 28),
+        related=[
+            {
+                "prefect.resource.id": "vogon.ship.the-business-end",
+                "prefect.resource.role": "locale",
+            }
+        ],
+        payload={"text": "Oh freddled gruntbuggly..."},
+        id=UUID(int=1),
+    )
+
+    time.sleep(0.1)
+
+    assert isinstance(asserting_events_worker._client, AssertingEventsClient)
+
+    assert len(asserting_events_worker._client.events) == 1
+    event = asserting_events_worker._client.events[0]
+    assert event.event == "vogon.poetry.read"
+    assert event.resource.id == "vogon.poem.oh-freddled-gruntbuggly"
+    assert event.occurred == DateTimeTZ(2023, 3, 1, 12, 39, 28)
+    assert len(event.related) == 1
+    assert event.related[0].id == "vogon.ship.the-business-end"
+    assert event.related[0].role == "locale"
+    assert event.payload == {"text": "Oh freddled gruntbuggly..."}
+    assert event.id == UUID(int=1)