Skip to content

Commit

Permalink
Initializes telemetry UI
Browse files Browse the repository at this point in the history
Design:

1. CRA + tailwind (they don't like CRA but I really don't care)
2. prettier + eslint
3. Add to pre-commit-config.yaml rahte rthan husky (which is very
   painful IMO)
  • Loading branch information
elijahbenizzy committed Feb 20, 2024
1 parent 1d228f7 commit 0ed7eab
Show file tree
Hide file tree
Showing 86 changed files with 24,394 additions and 47 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
sphinx-build docs _build
- name: Deploy to GitHub Pages
uses: peaceiris/actions-gh-pages@v3
# if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
# if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/initial-prototypes' }}
with:
publish_branch: gh-pages
Expand Down
24 changes: 12 additions & 12 deletions .github/workflows/python-package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,17 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.9", "3.10", "3.11", "3.12"]
python-version: ['3.9', '3.10', '3.11', '3.12']

steps:
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v3
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install -e ".[tests,tracking-client]"
- name: Run tests
run: |
python -m pytest tests
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v3
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install -e ".[tests,tracking-client]"
- name: Run tests
run: |
python -m pytest tests
85 changes: 68 additions & 17 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,80 @@
# $ cd /PATH/TO/REPO
# $ pre-commit install

exclude: '^telemetry/ui'
repos:
- repo: https://github.com/ambv/black
- repo: https://github.com/ambv/black
rev: 23.11.0
hooks:
- id: black
args: [--line-length=100]
- repo: https://github.com/pre-commit/pre-commit-hooks
- id: black
args: [--line-length=100]
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
hooks:
- id: trailing-whitespace
# ensures files are either empty or end with a blank line
- id: end-of-file-fixer
# sorts requirements
- id: requirements-txt-fixer
# valid python file
- id: check-ast
# isort python package import sorting
- repo: https://github.com/pycqa/isort
- id: trailing-whitespace
# ensures files are either empty or end with a blank line
- id: end-of-file-fixer
# sorts requirements
- id: requirements-txt-fixer
# valid python file
- id: check-ast
# isort python package import sorting
- repo: https://github.com/pycqa/isort
rev: '5.12.0'
hooks:
- id: isort
args: ["--profile", "black", "--line-length=100", "--known-local-folder", "tests", "-p", "hamilton"]
- repo: https://github.com/pycqa/flake8
- id: isort
args:
[
'--profile',
'black',
'--line-length=100',
'--known-local-folder',
'tests',
'-p',
'hamilton'
]
- repo: https://github.com/pycqa/flake8
rev: 6.1.0
hooks:
- id: flake8
- id: flake8
- repo: local
# This is a bit of a hack, but its the easiest way to get it to all run together
# https://stackoverflow.com/questions/64001471/pylint-with-pre-commit-and-esllint-with-husky
hooks:
- id: frontend-lint-staged
name: frontend-lint-staged
language: system
entry: npx --prefix telemetry/ui lint-staged
pass_filenames: false
always_run: true
# - repo: https://github.com/pre-commit/mirrors-eslint
# rev: v8.56.0
# hooks:
# - id: eslint
# # Specify files to include, using regex to match the path
# # Optionally specify additional arguments to ESLint
# args:
# [
# '--config',
# 'telemetry/ui/.eslintrc.js',
# '--ignore-path',
# 'telemetry/ui/.eslintignore'
# ]
# files: '\.[jt]sx?$'
# additional_dependencies:
# - [email protected]
# - [email protected]
# - [email protected]
# - [email protected]
# - repo: https://github.com/pre-commit/mirrors-prettier
# rev: v3.1.0
# hooks:
# - id: prettier
# args:
# [
# '--config',
# 'telemetry/ui/.prettierrc.json',
# '--ignore-path',
# 'telemetry/ui/.prettierignore'
# ]
# files: '^telemetry/ui/.*\.(ts|tsx)$'
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ And a lot more!
Using hooks and other integrations you can (a) integrate with any of your favorite vendors (LLM observability, storage, etc...), and
(b) build custom actions that delegate to your favorite libraries.

Bur will *not* tell you how to build your models, how to query APIs, or how to manage your data. It will help you tie all these together
Bur will _not_ tell you how to build your models, how to query APIs, or how to manage your data. It will help you tie all these together
in a way that scales with your needs and makes following the logic of your system easy. Burr comes out of the box with a host of integrations
including tooling to build a UI in streamlit and watch your state machine execute.

Expand All @@ -33,7 +33,6 @@ We imagine a world in which Burr and Hamilton lived in harmony and saw through t
built Burr as a _harness_ to handle state between executions of Hamilton DAGs,
but realized that it has a wide array of applications and decided to release it.


# Getting Started

To get started, install from `pypi`, using your favorite package manager:
Expand Down
4 changes: 2 additions & 2 deletions burr/core/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,13 +211,13 @@ def step(self, inputs: Optional[Dict[str, Any]] = None) -> Optional[Tuple[Action
:return: Tuple[Function, dict, State] -- the function that was just ran, the result of running it, and the new state
"""
next_action = self.get_next_action()
if next_action is None:
return None
if inputs is None:
inputs = {}
self._adapter_set.call_all_lifecycle_hooks_sync(
"pre_run_step", action=next_action, state=self._state
)
if next_action is None:
return None
exc = None
result = None
new_state = self._state
Expand Down
2 changes: 1 addition & 1 deletion burr/tracking/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from burr.integrations.base import require_plugin
from burr.lifecycle import PostRunStepHook, PreRunStepHook
from burr.lifecycle.base import PostApplicationCreateHook
from burr.tracking.models import ApplicationModel, BeginEntryModel, EndEntryModel
from burr.tracking.common.models import ApplicationModel, BeginEntryModel, EndEntryModel

logger = logging.getLogger(__name__)

Expand Down
File renamed without changes.
146 changes: 146 additions & 0 deletions burr/tracking/server/backend.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import abc
import json
import os.path
from typing import Sequence, TypeVar

import aiofiles
import aiofiles.os as aiofilesos
import fastapi

from burr.tracking.common.models import BeginEntryModel, EndEntryModel
from burr.tracking.server import schema
from burr.tracking.server.schema import ApplicationLogs, ApplicationSummary

T = TypeVar("T")


class BackendBase(abc.ABC):
@abc.abstractmethod
async def list_projects(self, request: fastapi.Request) -> Sequence[schema.Project]:
"""Lists out all projects -- this relies on the paginate function to work properly.
:param request: The request object, used for authentication/authorization if needed
:return: the next page
"""
pass

@abc.abstractmethod
async def list_apps(
self, request: fastapi.Request, project_id: str
) -> Sequence[schema.ApplicationSummary]:
"""Lists out all apps (continual state machine runs with shared state) for a given project.
:param request: The request object, used for authentication/authorization if needed
:param project_id:
:return:
"""
pass

@abc.abstractmethod
async def get_application_logs(
self, request: fastapi.Request, project_id: str, app_id: str
) -> Sequence[schema.Step]:
"""Lists out all steps for a given app.
:param request: The request object, used for authentication/authorization if needed
:param app_id:
:return:
"""
pass


class LocalBackend(BackendBase):
DEFAULT_PATH = os.path.expanduser("~/.burr")

def __init__(self, path: str = DEFAULT_PATH):
self.path = path

async def list_projects(self, request: fastapi.Request) -> Sequence[schema.Project]:
out = []
if not os.path.exists(self.path):
return out
for entry in await aiofilesos.listdir(self.path):
full_path = os.path.join(self.path, entry)
if os.path.isdir(full_path):
out.append(
schema.Project(
name=entry,
id=entry,
uri=full_path, # TODO -- figure out what
last_written=await aiofilesos.path.getmtime(full_path),
created=await aiofilesos.path.getctime(full_path),
num_apps=len(await aiofilesos.listdir(full_path)),
)
)
return out

async def count_lines(self, file_path: str) -> int:
"""Quick tool to count lines"""
count = 0
async with aiofiles.open(file_path, "rb") as f:
async for _ in f:
count += 1
return count

async def list_apps(
self, request: fastapi.Request, project_id: str
) -> Sequence[ApplicationSummary]:
project_filepath = os.path.join(self.path, project_id)
if not os.path.exists(project_filepath):
raise fastapi.HTTPException(status_code=404, detail=f"Project: {project_id} not found")
out = []
for entry in await aiofilesos.listdir(project_filepath):
full_path = os.path.join(project_filepath, entry)
log_path = os.path.join(full_path, "log.jsonl")
if os.path.isdir(full_path):
out.append(
schema.ApplicationSummary(
app_id=entry,
first_written=await aiofilesos.path.getctime(full_path),
last_written=await aiofilesos.path.getmtime(full_path),
num_steps=await self.count_lines(log_path) // 2,
tags={},
)
)
return out

async def get_application_logs(
self, request: fastapi.Request, project_id: str, app_id: str
) -> ApplicationLogs:
app_filepath = os.path.join(self.path, project_id, app_id)
if not os.path.exists(app_filepath):
raise fastapi.HTTPException(
status_code=404, detail=f"App: {app_id} from project: {project_id} not found"
)
log_file = os.path.join(app_filepath, "log.jsonl")
graph_file = os.path.join(app_filepath, "graph.json")
if not os.path.exists(graph_file):
raise fastapi.HTTPException(
status_code=404,
detail=f"Graph file not found for app: "
f"{app_id} from project: {project_id}. "
f"Was this properly executed?",
)
steps = []
if os.path.exists(log_file):
steps = []
async with aiofiles.open(log_file) as f:
for i, line in enumerate(await f.readlines()):
json_line = json.loads(line)
if json_line["type"] == "begin_entry":
begin_step = BeginEntryModel.parse_obj(json_line)
steps.append(
schema.Step(
step_start_log=begin_step,
step_end_log=None,
step_sequence_id=i // 2,
)
)
else:
steps[-1].step_end_log = EndEntryModel.parse_obj(json_line)

async with aiofiles.open(graph_file) as f:
str_graph = await f.read()
return ApplicationLogs(
application=schema.ApplicationModel.parse_raw(str_graph), steps=steps
)
45 changes: 45 additions & 0 deletions burr/tracking/server/run.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
from typing import Sequence

from fastapi import FastAPI, Request

from burr.tracking.server import backend, schema
from burr.tracking.server.schema import ApplicationLogs

app = FastAPI()

backend = backend.LocalBackend()


@app.get("/api/v0/projects", response_model=Sequence[schema.Project])
async def get_projects(request: Request) -> Sequence[schema.Project]:
"""Gets all projects visible by the user.
:param request: FastAPI request
:return: a list of projects visible by the user
"""
return await backend.list_projects(request)


@app.get("/api/v0/{project_id}/apps", response_model=Sequence[schema.ApplicationSummary])
async def get_apps(request: Request, project_id: str) -> Sequence[schema.ApplicationSummary]:
"""Gets all apps visible by the user
:param request: FastAPI request
:param project_id: project name
:return: a list of projects visible by the user
"""
return await backend.list_apps(request, project_id)


@app.get("/api/v0/{project_id}/{app_id}/apps")
async def get_application_logs(request: Request, project_id: str, app_id: str) -> ApplicationLogs:
"""Lists steps for a given App.
TODO: add streaming capabilities for bi-directional communication
TODO: add pagination for quicker loading
:param request: FastAPI
:param project_id: ID of the project
:param app_id: ID of the associated application
:return: A list of steps with all associated step data
"""
return await backend.get_application_logs(request, project_id=project_id, app_id=app_id)
1 change: 1 addition & 0 deletions burr/tracking/server/run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
uvicorn run:app --host 0.0.0.0 --port 7241
Loading

0 comments on commit 0ed7eab

Please sign in to comment.