-
Notifications
You must be signed in to change notification settings - Fork 285
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix(python): application launch on Windows (#4276)
- Loading branch information
1 parent
c8de7f6
commit 9ede0a3
Showing
35 changed files
with
327 additions
and
70 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
1 change: 0 additions & 1 deletion
1
...test_multiple_classify_calls_anthropic.py → ...test_multiple_classify_calls_anthropic.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
1 change: 0 additions & 1 deletion
1
...ls/test_multiple_classify_calls_openai.py → ...ls/test_multiple_classify_calls_openai.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
1 change: 0 additions & 1 deletion
1
...ls/test_multiple_classify_calls_vertex.py → ...ls/test_multiple_classify_calls_vertex.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
[mypy] | ||
strict = true | ||
explicit_package_bases = true |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
[pytest] | ||
addopts = -raP -l |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
faker | ||
openinference-semantic-conventions | ||
opentelemetry-sdk | ||
portpicker | ||
psutil | ||
types-psutil |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
line-length = 100 | ||
target-version = "py38" | ||
|
||
[lint] | ||
select = ["E", "F", "W", "I", "NPY201"] | ||
|
||
[lint.isort] | ||
force-single-line = false | ||
|
||
[lint.per-file-ignores] | ||
"*.ipynb" = ["E402", "E501"] | ||
|
||
[format] | ||
line-ending = "native" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
import os | ||
import tempfile | ||
from typing import Iterator, List | ||
from unittest import mock | ||
from urllib.parse import urljoin | ||
|
||
import pytest | ||
from _pytest.monkeypatch import MonkeyPatch | ||
from faker import Faker | ||
from openinference.semconv.resource import ResourceAttributes | ||
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter as GRPCExporter | ||
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter as HTTPExporter | ||
from opentelemetry.sdk.resources import Resource | ||
from opentelemetry.sdk.trace import TracerProvider | ||
from opentelemetry.sdk.trace.export import SimpleSpanProcessor | ||
from opentelemetry.trace import Tracer | ||
from phoenix.config import ( | ||
ENV_PHOENIX_GRPC_PORT, | ||
ENV_PHOENIX_PORT, | ||
ENV_PHOENIX_WORKING_DIR, | ||
get_base_url, | ||
get_env_grpc_port, | ||
get_env_host, | ||
) | ||
from portpicker import pick_unused_port # type: ignore[import-untyped] | ||
|
||
|
||
@pytest.fixture(autouse=True) | ||
def set_env_var(monkeypatch: Iterator[MonkeyPatch]) -> Iterator[None]: | ||
tmp = tempfile.TemporaryDirectory() | ||
values = ( | ||
(ENV_PHOENIX_PORT, str(pick_unused_port())), | ||
(ENV_PHOENIX_GRPC_PORT, str(pick_unused_port())), | ||
(ENV_PHOENIX_WORKING_DIR, tmp.name), | ||
) | ||
try: | ||
with mock.patch.dict(os.environ, values): | ||
yield | ||
finally: | ||
try: | ||
# This is for Windows. In Python 3.10+, it's cleaner to use | ||
# `TemporaryDirectory(ignore_cleanup_errors=True)` instead. | ||
tmp.cleanup() | ||
except BaseException: | ||
pass | ||
|
||
|
||
@pytest.fixture | ||
def tracers( | ||
project_name: str, | ||
fake: Faker, | ||
) -> List[Tracer]: | ||
host = get_env_host() | ||
if host == "0.0.0.0": | ||
host = "127.0.0.1" | ||
grpc_endpoint = f"http://{host}:{get_env_grpc_port()}" | ||
http_endpoint = urljoin(get_base_url(), "v1/traces") | ||
tracers = [] | ||
resource = Resource({ResourceAttributes.PROJECT_NAME: project_name}) | ||
for exporter in (GRPCExporter(grpc_endpoint), HTTPExporter(http_endpoint)): | ||
tracer_provider = TracerProvider(resource=resource) | ||
tracer_provider.add_span_processor(SimpleSpanProcessor(exporter)) | ||
tracers.append(tracer_provider.get_tracer(__name__)) | ||
return tracers | ||
|
||
|
||
@pytest.fixture | ||
def fake() -> Faker: | ||
return Faker() | ||
|
||
|
||
@pytest.fixture | ||
def project_name(fake: Faker) -> str: | ||
return fake.pystr() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
import json | ||
import os | ||
import sys | ||
from contextlib import contextmanager | ||
from queue import SimpleQueue | ||
from subprocess import PIPE, STDOUT | ||
from threading import Thread | ||
from time import sleep, time | ||
from typing import Iterator, List, Set | ||
from urllib.parse import urljoin | ||
from urllib.request import Request, urlopen | ||
|
||
import pytest | ||
from faker import Faker | ||
from opentelemetry.trace import Tracer | ||
from phoenix.config import get_base_url | ||
from psutil import STATUS_ZOMBIE, Popen | ||
|
||
|
||
@pytest.fixture | ||
def req() -> Request: | ||
query = dict(query="query{projects{edges{node{name spans{edges{node{name}}}}}}}") | ||
return Request( | ||
method="POST", | ||
url=urljoin(get_base_url(), "graphql"), | ||
data=json.dumps(query).encode("utf-8"), | ||
headers={"Content-Type": "application/json"}, | ||
) | ||
|
||
|
||
def test_launch_app( | ||
tracers: List[Tracer], | ||
project_name: str, | ||
req: Request, | ||
fake: Faker, | ||
) -> None: | ||
span_names: Set[str] = set() | ||
for i in range(2): | ||
with launch(): | ||
for t, tracer in enumerate(tracers): | ||
name = f"{i}_{t}_{fake.pystr()}" | ||
span_names.add(name) | ||
tracer.start_span(name).end() | ||
sleep(2) | ||
response = urlopen(req) | ||
response_dict = json.loads(response.read().decode("utf-8")) | ||
assert response_dict | ||
assert not response_dict.get("errors") | ||
assert { | ||
span["node"]["name"] | ||
for project in response_dict["data"]["projects"]["edges"] | ||
for span in project["node"]["spans"]["edges"] | ||
if project["node"]["name"] == project_name | ||
} == span_names | ||
print(f"{response_dict=}") | ||
|
||
|
||
@contextmanager | ||
def launch() -> Iterator[None]: | ||
command = f"{sys.executable} -m phoenix.server.main --no-ui serve" | ||
process = Popen(command.split(), stdout=PIPE, stderr=STDOUT, text=True, env=os.environ) | ||
log: "SimpleQueue[str]" = SimpleQueue() | ||
Thread(target=capture_stdout, args=(process, log), daemon=True).start() | ||
t = 60 | ||
time_limit = time() + t | ||
timed_out = False | ||
url = urljoin(get_base_url(), "healthz") | ||
while not timed_out and is_alive(process): | ||
sleep(0.1) | ||
try: | ||
urlopen(url) | ||
break | ||
except BaseException: | ||
timed_out = time() > time_limit | ||
try: | ||
if timed_out: | ||
raise TimeoutError(f"Server did not start within {t} seconds.") | ||
assert is_alive(process) | ||
yield | ||
process.terminate() | ||
process.wait(10) | ||
finally: | ||
logs = [] | ||
while not log.empty(): | ||
# For unknown reasons, this hangs if we try to print immediately | ||
# after `get()`, so we collect the lines and print them later. | ||
logs.append(log.get()) | ||
for line in logs: | ||
print(line, end="") | ||
|
||
|
||
def is_alive(process: Popen) -> bool: | ||
return process.is_running() and process.status() != STATUS_ZOMBIE | ||
|
||
|
||
def capture_stdout(process: Popen, log: "SimpleQueue[str]") -> None: | ||
while True: | ||
log.put(process.stdout.readline()) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.