Skip to content

Commit

Permalink
[JAC-MINI]: Initial
Browse files Browse the repository at this point in the history
  • Loading branch information
amadolid committed Sep 19, 2024
1 parent c612032 commit 84e7dd0
Show file tree
Hide file tree
Showing 16 changed files with 3,013 additions and 1 deletion.
5 changes: 5 additions & 0 deletions jac-mini/.flake8
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[flake8]
exclude = fixtures, __jac_gen__
plugins = flake8_import_order, flake8_docstrings, flake8_comprehensions, flake8_bugbear, flake8_annotations, pep8_naming, flake8_simplify
max-line-length = 120
ignore = E203, W503, ANN101, ANN102
53 changes: 53 additions & 0 deletions jac-mini/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Django #
*.log
*.pot
*.pyc
__pycache__
db.sqlite3
media
migrations
venv
__jac_gen__/
*.jir

# Distribution / packaging
.Python
play/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# Others #
.env
.coverage
.session
.DS_Store
build
.vscode
*Zone.Identifier
.DS_Store
parser.out
codegen_output*

*.rdb
node_modules
out.dot
out.txt

# Mypy files #
.mypy_cache*
.jac_mypy_cache
27 changes: 27 additions & 0 deletions jac-mini/.pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.6.0
hooks:
- id: check-yaml
args: [--allow-multiple-documents]
- id: check-json
- id: trailing-whitespace
- repo: https://github.com/psf/black
rev: 24.4.2
hooks:
- id: black
- repo: https://github.com/PyCQA/flake8
rev: 7.1.0
hooks:
- id: flake8
args: ["--config=jac-mini/.flake8"]
additional_dependencies: [pep8-naming, flake8_import_order, flake8_docstrings, flake8_comprehensions, flake8_bugbear, flake8_annotations, flake8_simplify]
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.8.0
hooks:
- id: mypy
additional_dependencies: [types-redis, motor-types]
exclude: 'venv|__jac_gen__|tests|setup.py'
args:
- --follow-imports=silent
- --ignore-missing-imports
21 changes: 21 additions & 0 deletions jac-mini/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2024 Jaseci-Labs

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
11 changes: 11 additions & 0 deletions jac-mini/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# **jac-cloud**

### Installation
```bash
pip install -e .
```
### Dependencies for Devs
```bash
pip install jaclang black pre-commit flake8 flake8_import_order flake8_docstrings flake8_comprehensions flake8_bugbear flake8_annotations pep8_naming flake8_simplify mypy pytest
pre-commit install
```
1 change: 1 addition & 0 deletions jac-mini/jac_mini/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""JacLang Jaseci."""
1 change: 1 addition & 0 deletions jac-mini/jac_mini/plugin/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Jaseci Plugins."""
198 changes: 198 additions & 0 deletions jac-mini/jac_mini/plugin/cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
"""Module for registering CLI plugins for jaseci."""

from dataclasses import Field, MISSING, asdict, fields, is_dataclass
from inspect import isclass
from os import getenv, path
from pickle import load
from typing import Any, Type, cast, get_type_hints

from asyncer import syncify

from fastapi import Depends, FastAPI, File, Response, UploadFile, status
from fastapi.responses import ORJSONResponse

from jaclang import jac_import
from jaclang.cli.cmdreg import cmd_registry
from jaclang.plugin.default import hookimpl
from jaclang.runtimelib.architype import (
Anchor,
Architype,
WalkerAnchor,
WalkerArchitype,
)
from jaclang.runtimelib.context import ExecutionContext
from jaclang.runtimelib.machine import JacMachine, JacProgram

from orjson import loads

from pydantic import BaseModel, Field as pyField, ValidationError, create_model

from starlette.datastructures import UploadFile as BaseUploadFile

from uvicorn import run

FILE_TYPES = {
UploadFile,
list[UploadFile],
UploadFile | None,
list[UploadFile] | None,
}


def response(reports: list[Any], status: int = 200) -> dict[str, Any]:
"""Return serialized version of reports."""
resp: dict[str, Any] = {"status": status}

for key, val in enumerate(reports):
clean_response(key, val, reports)
resp["reports"] = reports

return resp


def clean_response(key: str | int, val: Any, obj: list | dict) -> None: # noqa: ANN401
"""Cleanup and override current object."""
match val:
case list():
for idx, lval in enumerate(val):
clean_response(idx, lval, val)
case dict():
for key, dval in val.items():
clean_response(key, dval, val)
case Anchor():
cast(dict, obj)[key] = asdict(val.report())
case Architype():
cast(dict, obj)[key] = asdict(val.__jac__.report())
case val if is_dataclass(val) and not isinstance(val, type):
cast(dict, obj)[key] = asdict(val)
case _:
pass


def gen_model_field(cls: type, field: Field, is_file: bool = False) -> tuple[type, Any]:
"""Generate Specs for Model Field."""
if field.default is not MISSING:
consts = (cls, pyField(default=field.default))
elif callable(field.default_factory):
consts = (cls, pyField(default_factory=field.default_factory))
else:
consts = (cls, File(...) if is_file else ...)

return consts


def populate_apis(app: FastAPI, cls: Type[WalkerArchitype]) -> None:
"""Generate FastAPI endpoint based on WalkerArchitype class."""
body: dict[str, Any] = {}
files: dict[str, Any] = {}

hintings = get_type_hints(cls)
for f in fields(cls):
f_name = f.name
f_type = hintings[f_name]
if f_type in FILE_TYPES:
files[f_name] = gen_model_field(f_type, f, True)
else:
consts = gen_model_field(f_type, f)
body[f_name] = consts

payload: dict[str, Any] = {
"files": (
create_model(f"{cls.__name__.lower()}_files_model", **files),
Depends(),
),
}

body_model = None
if body:
body_model = create_model(f"{cls.__name__.lower()}_body_model", **body)

if files:
payload["body"] = (UploadFile, File(...))
else:
payload["body"] = (body_model, ...)

payload_model = create_model(f"{cls.__name__.lower()}_request_model", **payload)

def api_entry(
node: str | None,
payload: payload_model = Depends(), # type: ignore # noqa: B008
) -> ORJSONResponse:
pl = cast(BaseModel, payload).model_dump()
body = pl.get("body", {})

if isinstance(body, BaseUploadFile) and body_model:
body = loads(syncify(body.read)())
try:
body = body_model(**body).model_dump()
except ValidationError as e:
return ORJSONResponse({"detail": e.errors()})

jctx = ExecutionContext.create(session=getenv("DATABASE", "database"))
jctx.set_entry_node(node)

wlk: WalkerAnchor = cls(**body, **pl["files"]).__jac__
wlk.spawn_call(jctx.entry_node)
jctx.close()
return ORJSONResponse(response(jctx.reports))

def api_root(
payload: payload_model = Depends(), # type: ignore # noqa: B008
) -> Response:
return api_entry(None, payload)

app.post(url := f"/{cls.__name__}", summary=url)(api_root)
app.post(url := f"/{cls.__name__}/{{node}}", summary=url)(api_entry)


class JacCmd:
"""Jac CLI."""

@staticmethod
@hookimpl
def create_cmd() -> None:
"""Create Jac CLI cmds."""

@cmd_registry.register
def serve(filename: str, host: str = "0.0.0.0", port: int = 8000) -> None:
"""Serve the jac application."""
base, mod = path.split(filename)
base = base if base else "./"
mod = mod[:-4]

if filename.endswith(".jac"):
(module,) = jac_import(
target=mod,
base_path=base,
cachable=True,
override_name="__main__",
)
elif filename.endswith(".jir"):
with open(filename, "rb") as f:
JacMachine(base).attach_program(
JacProgram(mod_bundle=load(f), bytecode=None)
)
(module,) = jac_import(
target=mod,
base_path=base,
cachable=True,
override_name="__main__",
)
else:
JacMachine.detach()
raise ValueError("Not a valid file!\nOnly supports `.jac` and `.jir`")

app = FastAPI()

@app.get("/", status_code=status.HTTP_200_OK)
def healthz() -> Response:
"""Healthz API."""
return Response()

for obj in module.__dict__.values():
if isclass(obj) and issubclass(obj, WalkerArchitype):
populate_apis(app, obj)

run(app, host=host, port=port)

JacMachine.detach()
16 changes: 16 additions & 0 deletions jac-mini/jac_mini/plugin/jaseci.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
"""Jac Language Features."""

from typing import Any

from jaclang.plugin.default import hookimpl
from jaclang.plugin.feature import JacFeature as Jac


class JacPlugin:
"""Jaseci Implementations."""

@staticmethod
@hookimpl
def report(expr: Any) -> None: # noqa:ANN401
"""Jac's report stmt feature."""
Jac.get_context().reports.append(expr)
1 change: 1 addition & 0 deletions jac-mini/jac_mini/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Jaseci Unit Tests."""
Loading

0 comments on commit 84e7dd0

Please sign in to comment.