Skip to content
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

Feat: add documents #59

Draft
wants to merge 6 commits into
base: fix/nada-type-nada-value-split
Choose a base branch
from
Draft
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
21 changes: 21 additions & 0 deletions nada_dsl/ast_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -419,3 +419,24 @@ def to_mir(self):
"source_ref_index": self.source_ref.to_index(),
}
}

@dataclass
class DocumentAccessorASTOperation(ASTOperation):
"""AST representation of an document accessor operation."""

key: str
source: int

def child_operations(self):
return [self.source]

def to_mir(self):
return {
"DocumentAccessor": {
"id": self.id,
"key": self.key,
"source": self.source,
"type": self.ty,
"source_ref_index": self.source_ref.to_index(),
}
}
2 changes: 2 additions & 0 deletions nada_dsl/compiler_frontend.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from typing import List, Dict, Any, Optional, Tuple
from sortedcontainers import SortedDict

from nada_dsl import DocumentAccessorASTOperation
from nada_dsl.ast_util import (
AST_OPERATIONS,
ASTOperation,
Expand Down Expand Up @@ -300,6 +301,7 @@ def process_operation(
NadaFunctionArgASTOperation,
NTupleAccessorASTOperation,
ObjectAccessorASTOperation,
DocumentAccessorASTOperation,
),
):
processed_operation = ProcessOperationOutput(operation.to_mir(), None)
Expand Down
3 changes: 0 additions & 3 deletions nada_dsl/nada_types/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,6 @@ def is_numeric(self) -> bool:


# TODO: abstract?
@dataclass
class DslType:
"""Nada type class.

Expand All @@ -136,8 +135,6 @@ class DslType:

"""

child: OperationType

def __init__(self, child: OperationType):
"""NadaType default constructor

Expand Down
137 changes: 126 additions & 11 deletions nada_dsl/nada_types/collections.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
"""Nada Collection type definitions."""

import os
from dataclasses import dataclass
import inspect
import json
from typing import Any, Dict, Generic, List
import typing

Expand All @@ -12,7 +14,7 @@
NewASTOperation,
ObjectAccessorASTOperation,
ReduceASTOperation,
UnaryASTOperation,
UnaryASTOperation, DocumentAccessorASTOperation,
)
from nada_dsl.nada_types import DslType

Expand All @@ -27,6 +29,8 @@
from nada_dsl.nada_types.function import NadaFunction, create_nada_fn
from nada_dsl.nada_types.generics import U, T, R
from . import AllTypes, AllTypesType, DslTypeRepr, OperationType
from jsonschema import Draft7Validator
from jsonschema.exceptions import SchemaError


def is_primitive_integer(nada_type_str: str):
Expand Down Expand Up @@ -108,7 +112,7 @@ def store_in_ast(self, ty):
)


class TupleType(DslType):
class TupleType(NadaType):
"""Marker type for Tuples."""

is_compound = True
Expand Down Expand Up @@ -165,18 +169,18 @@ def type(self):
return TupleType(self.left_type, self.right_type)


def _generate_accessor(ty: Any, accessor: Any) -> DslType:
def _wrap_accessor_with_type(ty: Any, accessor: Any) -> DslType:
if hasattr(ty, "ty") and ty.ty.is_literal(): # TODO: fix
raise TypeError("Literals are not supported in accessors")
return ty.instantiate(accessor)


class NTupleType(DslType):
class NTupleType(NadaType):
"""Marker type for NTuples."""

is_compound = True

def __init__(self, types: List[DslType]):
def __init__(self, types: List[NadaType]):
self.types = types

def instantiate(self, child_or_value):
Expand Down Expand Up @@ -224,7 +228,7 @@ def __getitem__(self, index: int) -> DslType:
source_ref=SourceRef.back_frame(),
)

return _generate_accessor(self.types[index], accessor)
return _wrap_accessor_with_type(self.types[index], accessor)

def type(self):
"""Metatype for NTuple"""
Expand Down Expand Up @@ -261,12 +265,12 @@ def store_in_ast(self, ty: object):
)


class ObjectType(DslType):
class ObjectType(NadaType):
"""Marker type for Objects."""

is_compound = True

def __init__(self, types: Dict[str, DslType]):
def __init__(self, types: Dict[str, NadaType]):
self.types = types

def to_mir(self):
Expand Down Expand Up @@ -314,7 +318,7 @@ def __getattr__(self, attr: str) -> DslType:
source_ref=SourceRef.back_frame(),
)

return _generate_accessor(self.types[attr], accessor)
return _wrap_accessor_with_type(self.types[attr], accessor)

def type(self):
"""Metatype for Object"""
Expand Down Expand Up @@ -350,6 +354,117 @@ def store_in_ast(self, ty: object):
ty=ty,
)

class DocumentType(NadaType):
"""Marker type for Objects."""

is_compound = True

def __init__(self, types: Dict[str, NadaType]):
self.types = types

def to_mir(self):
"""Convert an object into a Nada type."""
return {
"Document": {"types": {name: ty.to_mir() for name, ty in self.types.items()}}
}

def instantiate(self, child_or_value):
return Document(child_or_value, self.types)

class Document(DslType):
"""The Document type"""

def __init__(self, child, filepath: str = None, public: bool = False, schema: dict = None):
if not schema:
with open(filepath, "r") as schema_file:
schema = json.load(schema_file)

try:
Draft7Validator.check_schema(schema)
except SchemaError as e:
raise TypeError("Schema validation error:", e.message)


self.__schema = schema
self.__public = public
super().__init__(child)

def __getattr__(self, item):
if item not in self.__schema["properties"]:
raise AttributeError(
f"'Document has no attribute '{item}'"
)

accessor = DocumentAccessor(
key=item,
child=self,
source_ref=SourceRef.back_frame(),
)

attribute_type = self.__schema_to_nada_type(self.__schema["properties"][item])

return _wrap_accessor_with_type(attribute_type, accessor)

def __schema_to_nada_type(self, schema: dict) -> NadaType:
IntegerType = PublicIntegerType if self.__public else SecretIntegerType
UnsignedIntegerType = PublicUnsignedIntegerType if self.__public else SecretUnsignedIntegerType
BooleanType = PublicBooleanType if self.__public else SecretBooleanType
if schema["type"] == "integer" and "nada_type" in schema and schema["nada_type"] == "unsigned":
return UnsignedIntegerType()
if schema["type"] == "integer":
return IntegerType()
elif schema["type"] == "boolean":
return BooleanType()
elif schema["type"] == "object":
return ObjectType(types={key: self.__schema_to_nada_type(value) for key, value in schema["properties"].items()})
elif schema["type"] == "array" and "prefixItems" in schema:
return NTupleType([self.__schema_to_nada_type(value) for value in schema["prefixItems"]])
elif schema["type"] == "array":
if "size" not in schema:
raise TypeError("size not defined in array schema")
return ArrayType(contained_type=self.__schema_to_nada_type(schema["items"]), size=schema["size"])
else:
raise TypeError(f"type '{schema['type']}' not supported in json schema")

def type(self):
return DocumentType({key: self.__schema_to_nada_type(value) for key, value in self.__schema["properties"].items()})

class PublicDocument(Document):
def __init__(self, child, filepath: str):
super().__init__(child, filepath, True)

class SecretDocument(Document):
def __init__(self, child, filepath: str):
super().__init__(child, filepath, False)

@dataclass
class DocumentAccessor:
"""Accessor for Object"""

child: Object
key: str
source_ref: SourceRef

def __init__(
self,
child: Object,
key: str,
source_ref: SourceRef,
):
self.id = next_operation_id()
self.child = child
self.key = key
self.source_ref = source_ref

def store_in_ast(self, ty: object):
"""Store this accessor in the AST."""
AST_OPERATIONS[self.id] = DocumentAccessorASTOperation(
id=self.id,
source=self.child.child.id,
key=self.key,
source_ref=self.source_ref,
ty=ty,
)

class Zip:
"""The Zip operation."""
Expand Down Expand Up @@ -412,7 +527,7 @@ def store_in_ast(self, ty: DslTypeRepr):
)


class ArrayType(DslType):
class ArrayType(NadaType):
"""Marker type for arrays."""

is_compound = True
Expand Down
6 changes: 5 additions & 1 deletion nada_mir/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ classifiers = [
"Operating System :: OS Independent",
]

dependencies = ["grpcio-tools==1.62.3", "betterproto==2.0.0b7"]
dependencies = [
"grpcio-tools==1.62.3",
"betterproto==2.0.0b7",
"jsonschema==4.22.0",
]

[project.optional-dependencies]
dev = ["betterproto[compiler]==2.0.0b7"]
18 changes: 15 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,28 @@ dependencies = [
"parsial~=0.1",
"sortedcontainers~=2.4",
"typing_extensions~=4.12.2",
"jsonschema==4.22.0",
]
classifiers = ["License :: OSI Approved :: Apache Software License"]
license = { file = "LICENSE" }

[project.optional-dependencies]
docs = ["toml~=0.10.2", "sphinx>=5,<9", "sphinx-rtd-theme>=1.0,<3.1", "sphinx-autoapi~=3.3.2"]
docs = [
"toml~=0.10.2",
"sphinx>=5,<9",
"sphinx-rtd-theme>=1.0,<3.1",
"sphinx-autoapi~=3.3.2",
]
test = ["pytest>=7.4,<9.0", "pytest-cov>=4,<7"]
lint = ["pylint>=2.17,<3.4"]

[tool.setuptools]
packages = ["nada_dsl", "nada_dsl.audit", "nada_dsl.future", "nada_dsl.nada_types"]
packages = [
"nada_dsl",
"nada_dsl.audit",
"nada_dsl.future",
"nada_dsl.nada_types",
]

[tool.pytest.ini_options]
addopts = "--doctest-modules --ignore=docs --cov=nada_dsl --cov-report term-missing"
Expand All @@ -42,7 +53,8 @@ dev-dependencies = [
"tomli",
"requests",
"typing_extensions~=4.12.2",
"ruff>=0.8.0"
"ruff>=0.8.0",
"jsonschema==4.22.0",
]

[tool.uv.sources]
Expand Down
40 changes: 40 additions & 0 deletions test-programs/doc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"a": {
"type": "integer"
},
"b": {
"type": "boolean"
},
"c": {
"type": "object",
"properties": {
"c": {
"type": "integer"
}
}
},
"d": {
"type": "array",
"items": {
"type": "integer"
},
"size": 2
},
"e": {
"type": "array",
"prefixItems": [{"type": "integer"}, {"type": "boolean"}]
},
"f": {
"type": "integer",
"nada_type": "unsigned_integer"
}
},
"required": [
"a",
"b"
],
"additionalProperties": false
}
Loading
Loading