diff --git a/README.md b/docs/README.md
similarity index 80%
rename from README.md
rename to docs/README.md
index e587af3d..ce0941ef 100644
--- a/README.md
+++ b/docs/README.md
@@ -88,20 +88,4 @@ You can lint with:
And you can run tests with
- make cli-test
-
-### Trying out Numerous app engine development
-
-In the `examples/numerous` folder are two apps `action.py` (containing
-`ActionTool`), and `parameters.py` (containing `ParameterTool`). These can be
-used to test the Numerous app engine development features.
-
-**Note: You need an activate python environment with the python SDK installed.**
-See the [python sdk development section](#development-of-python-sdk-) for
-information about how to install it.
-
-For example, if you built using `make cli-build`, you can run
-
-```
-./build/numerous dev examples/numerous/parameters.py:ParameterApp
-```
+ make cli-test
\ No newline at end of file
diff --git a/docs/collections.md b/docs/collections.md
index 3f8174fd..a62afd31 100644
--- a/docs/collections.md
+++ b/docs/collections.md
@@ -9,12 +9,10 @@ Collections acts as a schemaless database, where users can store, retrieve, and
4. Tag documents and files with key/value tags, and filter documents and files
by these tags.
-!!! info
- This feature only supports apps that are deployed to Numerous.
-!!! info
- Remember to add `numerous` as a dependency in your project; most likely to
- your `requirements.txt` file.
+This feature only supports apps that are deployed to Numerous.
+
+Remember to add `numerous` as a dependency in your project; most likely to your `requirements.txt` file.
## Basic usage
@@ -59,6 +57,7 @@ else:
```
-Below is the API reference for working with collections in Numerous:
+## API Reference
-::: numerous.collection
+::: numerous.collections
+::: numerous.collections.collection.collection
diff --git a/examples/numerous/action.py b/examples/numerous/action.py
deleted file mode 100644
index b07103ba..00000000
--- a/examples/numerous/action.py
+++ /dev/null
@@ -1,13 +0,0 @@
-from numerous import action, app
-
-
-@app
-class ActionApp:
- count: float
- message: str
-
- @action
- def increment(self) -> None:
- self.count += 1
- self.message = f"Count now: {self.count}"
- print("Incrementing count:", self.count)
diff --git a/examples/numerous/container.py b/examples/numerous/container.py
deleted file mode 100644
index eef0487d..00000000
--- a/examples/numerous/container.py
+++ /dev/null
@@ -1,25 +0,0 @@
-from numerous import action, app, container
-
-
-@container
-class MyNestedContainer:
- nested_child: float
-
-
-@container
-class MyContainer:
- child: str
- nested: MyNestedContainer
-
- def child_updated(self) -> None:
- print("wow")
-
-
-@app
-class MyContainerApp:
- my_container: MyContainer
- output: str
-
- @action
- def set_output(self) -> None:
- self.output = f"First child is {self.my_container.child}, deep child is {self.my_container.nested.nested_child}"
diff --git a/examples/numerous/fields.py b/examples/numerous/fields.py
deleted file mode 100644
index 02cfedca..00000000
--- a/examples/numerous/fields.py
+++ /dev/null
@@ -1,22 +0,0 @@
-from numerous import app, field
-
-
-@app
-class FieldApp:
- field_text_default: str = field()
- field_number_default: float = field()
-
- my_text_field: str = field(label="Text Field Label")
- my_number_field: float = field(label="Number Field Label")
-
- my_text_field_no_label: str = field(default="My text field")
- my_number_field_no_label: float = field(default=42.0)
-
- my_text_field_with_default_value: str = field(
- label="Text Field Label",
- default="My text field",
- )
- my_number_field_with_default_value: float = field(
- label="Number Field Label",
- default=42.0,
- )
diff --git a/examples/numerous/html.py b/examples/numerous/html.py
deleted file mode 100644
index fee6a44c..00000000
--- a/examples/numerous/html.py
+++ /dev/null
@@ -1,32 +0,0 @@
-from numerous import app, html
-
-HTML_TEMPLATE = """
-
-
{title}: A HTML story
-
- There once was a mark-up language with which you could create anything you liked.
-
-
{story}
-
The end!
-
-"""
-DEFAULT_TITLE = "Numerous and the markup"
-DEFAULT_STORY = "And then, one day you found numerous to empower it even further!"
-
-
-@app
-class HTMLApp:
- title: str = DEFAULT_TITLE
- story: str = DEFAULT_STORY
- html_example: str = html(
- default=HTML_TEMPLATE.format(title=DEFAULT_TITLE, story=DEFAULT_STORY),
- )
-
- def set_html(self) -> None:
- self.html_example = HTML_TEMPLATE.format(title=self.title, story=self.story)
-
- def title_updated(self) -> None:
- self.set_html()
-
- def story_updated(self) -> None:
- self.set_html()
diff --git a/examples/numerous/importing.py b/examples/numerous/importing.py
deleted file mode 100644
index 64fe400d..00000000
--- a/examples/numerous/importing.py
+++ /dev/null
@@ -1 +0,0 @@
-from parameters import ParameterApp # noqa: F401
diff --git a/examples/numerous/parameters.py b/examples/numerous/parameters.py
deleted file mode 100644
index c6f2fb2e..00000000
--- a/examples/numerous/parameters.py
+++ /dev/null
@@ -1,12 +0,0 @@
-from numerous import app
-
-
-@app
-class ParameterApp:
- param1: str
- param2: float
- output: str
-
- def param1_updated(self):
- print("Got an update:", self.param1)
- self.output = f"output: {self.param1}"
diff --git a/examples/numerous/plot.py b/examples/numerous/plot.py
deleted file mode 100644
index e4fa255d..00000000
--- a/examples/numerous/plot.py
+++ /dev/null
@@ -1,40 +0,0 @@
-import math
-
-from numerous import app, field, slider
-from plotly import graph_objects as go
-
-
-@app
-class PlotlyExample:
- phase_shift: float = slider(default=1, label="Phase Shift")
- vertical_shift: float = slider(default=1, label="Vertical Shift")
- period: float = slider(default=1, min_value=1, max_value=100, label="Period")
- amplitude: float = slider(default=1, label="Amplitude")
- graph: go.Figure = field()
-
- def phase_shift_updated(self):
- print("Updated phase shift", self.phase_shift)
- self._graph()
-
- def vertical_shift_updated(self):
- print("Updated vertical shift", self.vertical_shift)
- self._graph()
-
- def period_updated(self):
- print("Updated period", self.period)
- self._graph()
-
- def amplitude_updated(self):
- print("Updated amplitude", self.amplitude)
- self._graph()
-
- def _graph(self):
- xs = [i / 10.0 for i in range(1000)]
- ys = [self._sin(x) for x in xs]
- self.graph = go.Figure(go.Scatter(x=xs, y=ys))
-
- def _sin(self, t: float):
- b = 2.0 * math.pi / self.period
- return (
- self.amplitude * math.sin(b * (t + self.phase_shift)) + self.vertical_shift
- )
diff --git a/examples/numerous/sliders.py b/examples/numerous/sliders.py
deleted file mode 100644
index 61c382a3..00000000
--- a/examples/numerous/sliders.py
+++ /dev/null
@@ -1,24 +0,0 @@
-from numerous import app, slider
-
-
-@app
-class SliderApp:
- result: float
- default_slider: float = slider()
- custom_slider: float = slider(
- default=10.0,
- label="My custom label",
- min_value=-20.0,
- max_value=20.0,
- )
-
- def _compute(self):
- self.result = self.default_slider * self.custom_slider
-
- def default_slider_updated(self):
- self._compute()
- print("default slider updated", self.default_slider, self.result)
-
- def custom_slider_updated(self):
- self._compute()
- print("custom slider updated", self.custom_slider, self.result)
diff --git a/examples/numerous/syntax.py b/examples/numerous/syntax.py
deleted file mode 100644
index 984c78e6..00000000
--- a/examples/numerous/syntax.py
+++ /dev/null
@@ -1,9 +0,0 @@
-# mypy: ignore-errors
-
-from numerous import app
-
-
-@app
-class SyntaxErrorApp:
- my_syntax_error str # noqa: E999 ]
- my_field: str
diff --git a/python/src/numerous/__init__.py b/python/src/numerous/__init__.py
index 2be0be7d..90c640bb 100644
--- a/python/src/numerous/__init__.py
+++ b/python/src/numerous/__init__.py
@@ -1,3 +1,3 @@
"""The python SDK for numerous."""
-from .collection import collection
+from .collections import collection
diff --git a/python/src/numerous/_client/_get_client.py b/python/src/numerous/_client/_get_client.py
index 562d5c07..50124840 100644
--- a/python/src/numerous/_client/_get_client.py
+++ b/python/src/numerous/_client/_get_client.py
@@ -2,7 +2,7 @@
from pathlib import Path
from typing import Optional
-from numerous.collection._client import Client
+from numerous.collections._client import Client
from numerous.generated.graphql.client import Client as GQLClient
from ._fs_client import FileSystemClient
diff --git a/python/src/numerous/_client/_graphql_client.py b/python/src/numerous/_client/_graphql_client.py
index 5baf4ecb..9d61a010 100644
--- a/python/src/numerous/_client/_graphql_client.py
+++ b/python/src/numerous/_client/_graphql_client.py
@@ -3,7 +3,7 @@
import os
from typing import Optional, Union
-from numerous.collection.exceptions import ParentCollectionNotFoundError
+from numerous.collections.exceptions import ParentCollectionNotFoundError
from numerous.generated.graphql.client import Client as GQLClient
from numerous.generated.graphql.collection_collections import (
CollectionCollectionsCollectionCreateCollection,
diff --git a/python/src/numerous/appdev/__init__.py b/python/src/numerous/appdev/__init__.py
deleted file mode 100644
index ebf8da88..00000000
--- a/python/src/numerous/appdev/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-"""Functionality related to the app development CLI."""
diff --git a/python/src/numerous/appdev/commands.py b/python/src/numerous/appdev/commands.py
deleted file mode 100644
index 7cb606ca..00000000
--- a/python/src/numerous/appdev/commands.py
+++ /dev/null
@@ -1,208 +0,0 @@
-"""App development CLI command implementations."""
-
-import json
-import logging
-import sys
-import traceback
-from dataclasses import asdict, dataclass
-from pathlib import Path
-from textwrap import indent
-from typing import Any, Optional, TextIO, Type
-
-from numerous.data_model import dump_data_model
-from numerous.generated.graphql import Client
-from numerous.session import Session
-from numerous.utils import AppT
-
-
-log = logging.getLogger(__name__)
-
-
-@dataclass
-class AppNotFoundError(Exception):
- app: str
- existing_apps: list[str]
-
-
-@dataclass
-class AppLoadRaisedError(Exception):
- traceback: str
- typename: str
-
-
-@dataclass
-class AppSyntaxError(Exception):
- msg: str
- context: str
- lineno: int
- offset: int
-
-
-def read_app(app_module: Path, app_class: str, output: TextIO = sys.stdout) -> None:
- try:
- app_cls = _read_app_definition(app_module, app_class)
- except Exception as e: # noqa: BLE001
- print_error(output, e)
- else:
- print_app(output, app_cls)
-
-
-def _transform_lineno(lineno: Optional[int]) -> int:
- if lineno is None:
- return 0
- return lineno - 2
-
-
-def _transform_offset(offset: Optional[int]) -> int:
- if offset is None:
- return 0
- return offset - 4
-
-
-def _read_app_definition(app_module: Path, app_class: str) -> Any: # noqa: ANN401
- scope: dict[str, Any] = {}
- module_text = app_module.read_text()
-
- # Check for syntax errors with raw code text
- try:
- compile(module_text, str(app_module), "exec")
- except SyntaxError as e:
- text = e.text or ""
- error_pointer = " " * ((e.offset or 0) - 1) + "^"
-
- # ensure newline between text and pointer
- if not text.endswith("\n"):
- text += "\n"
-
- raise AppSyntaxError(
- msg=e.msg,
- context=text + error_pointer,
- lineno=e.lineno or 0,
- offset=e.offset or 0,
- ) from e
-
- indented_module_text = indent(module_text, " ")
- exception_handled_module_text = (
- "try:\n"
- f"{indented_module_text}\n"
- "except ModuleNotFoundError:\n"
- " raise\n"
- "except BaseException as e:\n"
- " __numerous_read_error__ = e\n"
- )
-
- code = compile(exception_handled_module_text, str(app_module), "exec")
-
- exec(code, scope) # noqa: S102
-
- unknown_error = scope.get("__numerous_read_error__")
- if isinstance(unknown_error, BaseException):
- tb = traceback.TracebackException.from_exception(unknown_error)
-
- # handle inserted exception handler offsetting position
- for frame in tb.stack:
- if frame.filename == str(app_module):
- frame.lineno = _transform_lineno(frame.lineno)
- if sys.version_info >= (3, 11):
- frame.colno = _transform_offset(frame.colno)
- frame.end_lineno = _transform_lineno(frame.end_lineno)
- frame.end_colno = _transform_offset(frame.end_colno)
-
- raise AppLoadRaisedError(
- typename=type(unknown_error).__name__,
- traceback="".join(tb.format()),
- )
-
- try:
- return scope[app_class]
- except KeyError as err:
- raise AppNotFoundError(
- app_class,
- [
- app.__name__
- for app in scope.values()
- if getattr(app, "__numerous_app__", False)
- ],
- ) from err
-
-
-def print_app(output: TextIO, cls: Type[AppT]) -> None:
- data_model = dump_data_model(cls)
- output.write(json.dumps({"app": asdict(data_model)}))
- output.flush()
-
-
-def print_error(output: TextIO, error: Exception) -> None:
- if isinstance(error, AppNotFoundError):
- output.write(
- json.dumps(
- {
- "error": {
- "appnotfound": {
- "app": error.app,
- "found_apps": error.existing_apps,
- },
- },
- },
- ),
- )
- elif isinstance(error, AppSyntaxError):
- output.write(
- json.dumps(
- {
- "error": {
- "appsyntax": {
- "msg": error.msg,
- "context": error.context,
- "pos": {
- "line": error.lineno,
- "offset": error.offset,
- },
- },
- },
- },
- ),
- )
- output.flush()
- elif isinstance(error, ModuleNotFoundError):
- output.write(
- json.dumps(
- {
- "error": {
- "modulenotfound": {
- "module": error.name,
- },
- },
- },
- ),
- )
- output.flush()
- elif isinstance(error, AppLoadRaisedError):
- output.write(
- json.dumps(
- {
- "error": {
- "unknown": {
- "typename": error.typename,
- "traceback": error.traceback,
- },
- },
- },
- ),
- )
- output.flush()
-
-
-async def run_app_session(
- graphql_url: str,
- graphql_ws_url: str,
- session_id: str,
- app_module: Path,
- app_class: str,
-) -> None:
- log.info("running %s:%s in session %s", app_module, app_class, session_id)
- gql = Client(graphql_url, ws_url=graphql_ws_url)
- app_cls = _read_app_definition(app_module, app_class)
- session = await Session.initialize(session_id, gql, app_cls)
- await session.run()
- log.info("app session stopped")
diff --git a/python/src/numerous/apps.py b/python/src/numerous/apps.py
deleted file mode 100644
index 8a2f4c89..00000000
--- a/python/src/numerous/apps.py
+++ /dev/null
@@ -1,155 +0,0 @@
-"""Define applications with a dataclass-like interface."""
-
-from dataclasses import dataclass
-from types import MappingProxyType
-from typing import Any, Callable, Optional, Type, Union, overload
-
-from typing_extensions import dataclass_transform
-
-from numerous.utils import MISSING, AppT
-
-
-class HTML:
- def __init__(self, default: str) -> None:
- self.default = default
-
-
-def html( # type: ignore[no-untyped-def] # noqa: ANN201, PLR0913
- *,
- default: str = MISSING, # type: ignore[assignment]
- default_factory: Callable[[], str] = MISSING, # type: ignore[assignment] # noqa: ARG001
- init: bool = True, # noqa: ARG001
- repr: bool = True, # noqa: ARG001, A002
- hash: Optional[bool] = None, # noqa: ARG001, A002
- compare: bool = True, # noqa: ARG001
- metadata: Optional[MappingProxyType[str, Any]] = None, # noqa: ARG001
- kw_only: bool = MISSING, # type: ignore[assignment] # noqa: ARG001
-):
- return HTML(default)
-
-
-DEFAULT_FLOAT_MIN = 0.0
-DEFAULT_FLOAT_MAX = 100.0
-
-
-class Slider:
- def __init__( # noqa: PLR0913
- self,
- *,
- default: float = MISSING, # type: ignore[assignment]
- default_factory: Callable[[], float] = MISSING, # type: ignore[assignment] # noqa: ARG002
- init: bool = True, # noqa: ARG002
- repr: bool = True, # noqa: ARG002, A002
- hash: Optional[bool] = None, # noqa: ARG002, A002
- compare: bool = True, # noqa: ARG002
- metadata: Optional[MappingProxyType[str, Any]] = None, # noqa: ARG002
- kw_only: bool = MISSING, # type: ignore[assignment] # noqa: ARG002
- label: Optional[str] = None,
- min_value: float = DEFAULT_FLOAT_MIN,
- max_value: float = DEFAULT_FLOAT_MAX,
- ) -> None:
- self.default = default
- self.label = label
- self.min_value = min_value
- self.max_value = max_value
-
-
-def slider( # type: ignore[no-untyped-def] # noqa: ANN201, PLR0913
- *,
- default: float = MISSING, # type: ignore[assignment]
- default_factory: Callable[[], float] = MISSING, # type: ignore[assignment] # noqa: ARG001
- init: bool = True, # noqa: ARG001
- repr: bool = True, # noqa: ARG001, A002
- hash: Optional[bool] = None, # noqa: ARG001, A002
- compare: bool = True, # noqa: ARG001
- metadata: Optional[MappingProxyType[str, Any]] = None, # noqa: ARG001
- kw_only: bool = MISSING, # type: ignore[assignment] # noqa: ARG001
- label: Optional[str] = None,
- min_value: float = DEFAULT_FLOAT_MIN,
- max_value: float = DEFAULT_FLOAT_MAX,
-):
- return Slider(
- default=default,
- label=label,
- min_value=min_value,
- max_value=max_value,
- )
-
-
-class Field:
- def __init__( # noqa: PLR0913
- self,
- *,
- default: Union[str, float] = MISSING, # type: ignore[assignment]
- default_factory: Callable[[], Union[str, float]] = MISSING, # type: ignore[assignment]
- init: bool = True, # noqa: ARG002
- repr: bool = True, # noqa: ARG002, A002
- hash: Optional[bool] = None, # noqa: ARG002, A002
- compare: bool = True, # noqa: ARG002
- metadata: Optional[MappingProxyType[str, Any]] = None, # noqa: ARG002
- kw_only: bool = MISSING, # type: ignore[assignment] # noqa: ARG002
- label: Optional[str] = None,
- ) -> None:
- if default is MISSING and default_factory is not MISSING: # type: ignore[comparison-overlap]
- default = default_factory()
- self.default = default
- self.label = label
-
-
-def field( # type: ignore[no-untyped-def] # noqa: ANN201, PLR0913
- *,
- default: Union[str, float] = MISSING, # type: ignore[assignment]
- default_factory: Callable[[], float] = MISSING, # type: ignore[assignment]
- init: bool = True, # noqa: ARG001
- repr: bool = True, # noqa: ARG001, A002
- hash: Optional[bool] = None, # noqa: ARG001, A002
- compare: bool = True, # noqa: ARG001
- metadata: Optional[MappingProxyType[str, Any]] = None, # noqa: ARG001
- kw_only: bool = MISSING, # type: ignore[assignment] # noqa: ARG001
- label: Optional[str] = None,
-):
- return Field(default=default, default_factory=default_factory, label=label)
-
-
-@dataclass_transform()
-def container(cls: Type[AppT]) -> Type[AppT]:
- """Define a container."""
- cls.__container__ = True # type: ignore[attr-defined]
- return dataclass(cls)
-
-
-def action(action: Callable[[AppT], Any]) -> Callable[[AppT], Any]:
- """Define an action."""
- action.__action__ = True # type: ignore[attr-defined]
- return action
-
-
-@overload
-def app(cls: Type[AppT]) -> Type[AppT]: ...
-
-
-@overload
-def app(title: str = ...) -> Callable[[Type[AppT]], Type[AppT]]: ...
-
-
-def app(
- *args: Any,
- **kwargs: Any,
-) -> Union[Type[AppT], Callable[[Type[AppT]], Type[AppT]]]:
- invalid_error_message = "Invalid @app usage"
- if len(args) == 1 and not kwargs:
- return app_decorator()(args[0])
- if len(args) == 0 and "title" in kwargs:
- return app_decorator(**kwargs)
- raise ValueError(invalid_error_message)
-
-
-def app_decorator(**kwargs: dict[str, Any]) -> Callable[[Type[AppT]], Type[AppT]]:
- @dataclass_transform(field_specifiers=(field, html, slider))
- def decorator(cls: Type[AppT]) -> Type[AppT]:
- cls.__numerous_app__ = True # type: ignore[attr-defined]
- if title := kwargs.get("title"):
- cls.__title__ = title # type: ignore[attr-defined]
- return dataclass(cls)
-
- return decorator
diff --git a/python/src/numerous/collection/collection.py b/python/src/numerous/collection/collection.py
deleted file mode 100644
index 46e79311..00000000
--- a/python/src/numerous/collection/collection.py
+++ /dev/null
@@ -1,18 +0,0 @@
-"""Get or create a collection by name."""
-
-from typing import Optional
-
-from numerous._client._get_client import get_client
-from numerous.collection.numerous_collection import NumerousCollection
-
-from ._client import Client
-
-
-def collection(
- collection_key: str, _client: Optional[Client] = None
-) -> NumerousCollection:
- """Get or create a collection by name."""
- if _client is None:
- _client = get_client()
- collection_ref = _client.get_collection_reference(collection_key)
- return NumerousCollection(collection_ref, _client)
diff --git a/python/src/numerous/collection/__init__.py b/python/src/numerous/collections/__init__.py
similarity index 100%
rename from python/src/numerous/collection/__init__.py
rename to python/src/numerous/collections/__init__.py
diff --git a/python/src/numerous/collection/_client.py b/python/src/numerous/collections/_client.py
similarity index 100%
rename from python/src/numerous/collection/_client.py
rename to python/src/numerous/collections/_client.py
diff --git a/python/src/numerous/collections/collection.py b/python/src/numerous/collections/collection.py
new file mode 100644
index 00000000..ff3e6f3c
--- /dev/null
+++ b/python/src/numerous/collections/collection.py
@@ -0,0 +1,27 @@
+"""Collection API."""
+
+from typing import Optional
+
+from numerous._client._get_client import get_client
+from numerous.collections.numerous_collection import NumerousCollection
+
+from ._client import Client
+
+
+def collection(
+ collection_key: str, _client: Optional[Client] = None
+) -> NumerousCollection:
+ """Get or create a collection by name.
+
+ Args:
+ collection_key: The unique identifier for the collection.
+ _client: Optional client instance. If not provided, the default client will be used.
+
+ Returns:
+ NumerousCollection: An instance of NumerousCollection representing the requested collection.
+
+ """
+ if _client is None:
+ _client = get_client()
+ collection_ref = _client.get_collection_reference(collection_key)
+ return NumerousCollection(collection_ref, _client)
diff --git a/python/src/numerous/collection/exceptions.py b/python/src/numerous/collections/exceptions.py
similarity index 100%
rename from python/src/numerous/collection/exceptions.py
rename to python/src/numerous/collections/exceptions.py
diff --git a/python/src/numerous/collection/numerous_collection.py b/python/src/numerous/collections/numerous_collection.py
similarity index 72%
rename from python/src/numerous/collection/numerous_collection.py
rename to python/src/numerous/collections/numerous_collection.py
index ff29c05c..e030f94f 100644
--- a/python/src/numerous/collection/numerous_collection.py
+++ b/python/src/numerous/collections/numerous_collection.py
@@ -1,21 +1,36 @@
-"""Class for working with numerous collections."""
+"""Module for working with numerous collections."""
from typing import Iterator, Optional
-from numerous.collection._client import Client
-from numerous.collection.numerous_document import NumerousDocument
+from numerous.collections._client import Client
+from numerous.collections.numerous_document import NumerousDocument
from numerous.generated.graphql.fragments import CollectionReference
from numerous.generated.graphql.input_types import TagInput
class NumerousCollection:
+ """This class represents a collection in Numerous.
+
+ Args:
+ collection_ref: The collection reference.
+ _client: The client instance.
+ """
def __init__(self, collection_ref: CollectionReference, _client: Client) -> None:
+
self.key = collection_ref.key
self.id = collection_ref.id
self._client = _client
def collection(self, collection_key: str) -> Optional["NumerousCollection"]:
- """Get or create a collection by name."""
+ """Get or create a nested collection by name.
+
+ Args:
+ collection_key: The unique identifier for the collection.
+
+ Returns:
+ NumerousCollection: An instance of NumerousCollection representing the requested collection.
+
+ """
collection_ref = self._client.get_collection_reference(
collection_key=collection_key, parent_collection_id=self.id
)
@@ -28,9 +43,11 @@ def document(self, key: str) -> NumerousDocument:
"""
Get or create a document by key.
- Attributes
- ----------
- key (str): The key of the document.
+ Args:
+ key: The unique identifier for the document.
+
+ Returns:
+ NumerousDocument: An instance of NumerousDocument representing the requested document.
"""
numerous_doc_ref = self._client.get_collection_document(self.key, key)
@@ -52,17 +69,12 @@ def documents(
"""
Retrieve documents from the collection, filtered by a tag key and value.
- Parameters
- ----------
- tag_key : Optional[str]
- The key of the tag used to filter documents (optional).
- tag_value : Optional[str]
- The value of the tag used to filter documents (optional).
+ Args:
+ tag_key: The key of the tag used to filter documents (optional).
+ tag_value: The value of the tag used to filter documents (optional).
- Yields
- ------
- NumerousDocument
- Yields NumerousDocument objects from the collection.
+ Yields:
+ NumerousDocument: Yields NumerousDocument objects from the collection.
"""
end_cursor = ""
@@ -93,10 +105,8 @@ def collections(self) -> Iterator["NumerousCollection"]:
"""
Retrieve nested collections from the collection.
- Yields
- ------
- NumerousCollection
- Yields NumerousCollection objects.
+ Yields:
+ NumerousCollection: Yields NumerousCollection objects.
"""
end_cursor = ""
diff --git a/python/src/numerous/collection/numerous_document.py b/python/src/numerous/collections/numerous_document.py
similarity index 86%
rename from python/src/numerous/collection/numerous_document.py
rename to python/src/numerous/collections/numerous_document.py
index f4d54f64..681e8e63 100644
--- a/python/src/numerous/collection/numerous_document.py
+++ b/python/src/numerous/collections/numerous_document.py
@@ -2,7 +2,7 @@
from typing import Any, Optional
-from numerous.collection._client import Client
+from numerous.collections._client import Client
from numerous.generated.graphql.fragments import CollectionDocumentReference
from numerous.generated.graphql.input_types import TagInput
from numerous.jsonbase64 import base64_to_dict, dict_to_base64
@@ -12,10 +12,9 @@ class NumerousDocument:
"""
Represents a document in a Numerous collection.
- Attributes
- ----------
- key (str): The key of the document.
- collection_info tuple[str, str]: The id
+ Args:
+ key: The key of the document.
+ collection_info: The id and key of collection document belongs to.
and key of collection document belongs to.
data (Optional[dict[str, Any]]): The data of the document.
id (Optional[str]): The unique identifier of the document.
@@ -48,12 +47,20 @@ def __init__(
@property
def exists(self) -> bool:
- """Check if the document exists."""
+ """Check if the document exists.
+
+ Returns:
+ bool: True if the document exists, False otherwise.
+ """
return self.document_id is not None
@property
def tags(self) -> dict[str, str]:
- """Get the tags for the document."""
+ """Get the tags for the document.
+
+ Returns:
+ dict[str, str]: The tags for the document.
+ """
if self.document_id is not None:
return self._tags
@@ -65,11 +72,9 @@ def set(self, data: dict[str, Any]) -> None:
Set the data for the document.
Args:
- ----
- data (dict[str, Any]): The data to set for the document.
+ data: The data to set for the document.
Raises:
- ------
ValueError: If the document data setting fails.
"""
@@ -88,12 +93,10 @@ def get(self) -> Optional[dict[str, Any]]:
"""
Get the data of the document.
- Returns
- -------
+ Returns:
dict[str, Any]: The data of the document.
- Raises
- ------
+ Raises:
ValueError: If the document does not exist.
"""
@@ -107,8 +110,7 @@ def delete(self) -> None:
"""
Delete the document.
- Raises
- ------
+ Raises:
ValueError: If the document does not exist or deletion failed.
"""
@@ -131,12 +133,10 @@ def tag(self, key: str, value: str) -> None:
Add a tag to the document.
Args:
- ----
- key (str): The tag key.
- value (str): The tag value.
+ key: The tag key.
+ value: The tag value.
Raises:
- ------
ValueError: If the document does not exist.
"""
@@ -156,11 +156,9 @@ def tag_delete(self, tag_key: str) -> None:
Delete a tag from the document.
Args:
- ----
- tag_key (str): The key of the tag to delete.
+ tag_key: The key of the tag to delete.
Raises:
- ------
ValueError: If the document does not exist.
"""
@@ -176,7 +174,11 @@ def tag_delete(self, tag_key: str) -> None:
self._tags = {tag.key: tag.value for tag in tagged_document.tags}
def _fetch_data(self, document_key: str) -> None:
- """Fetch the data from the server."""
+ """Fetch the data from the server.
+
+ Args:
+ document_key: The key of the document to fetch.
+ """
if self.document_id is not None:
document = self._client.get_collection_document(
self.collection_key, document_key
diff --git a/python/src/numerous/updates.py b/python/src/numerous/updates.py
deleted file mode 100644
index 43d20fbc..00000000
--- a/python/src/numerous/updates.py
+++ /dev/null
@@ -1,180 +0,0 @@
-"""Handling of updates sent to the tool session instance."""
-
-import inspect
-import logging
-from types import MethodType
-from typing import Any, Callable, Optional, Union
-
-from numerous.generated.graphql import Updates
-from numerous.generated.graphql.updates import (
- UpdatesToolSessionEventToolSessionActionTriggered,
- UpdatesToolSessionEventToolSessionElementUpdated,
- UpdatesToolSessionEventToolSessionElementUpdatedElementHTMLElement,
- UpdatesToolSessionEventToolSessionElementUpdatedElementNumberField,
- UpdatesToolSessionEventToolSessionElementUpdatedElementSliderElement,
- UpdatesToolSessionEventToolSessionElementUpdatedElementTextField,
-)
-
-
-log = logging.getLogger(__name__)
-
-
-class ElementUpdateError(Exception):
- def __init__(self, element_name: str, instance_element_name: str) -> None:
- super().__init__(
- f"expected name {element_name} but instead found {instance_element_name}",
- )
-
-
-class UpdateHandler:
- """Updates app instances according to events sent to the app session."""
-
- def __init__(self, instance: object) -> None:
- self._instance = instance
- self._update_handlers = _find_instance_update_handlers(instance)
- self._actions = self._find_actions(instance)
-
- def _find_actions(self, instance: object) -> dict[str, Callable[[], Any]]:
- methods = inspect.getmembers(instance, predicate=inspect.ismethod)
- return {
- name: method
- for name, method in methods
- if getattr(method, "__action__", True)
- }
-
- def handle_update(self, updates: Updates) -> None:
- """Handle an update for the tool session."""
- event = updates.tool_session_event
- if isinstance(event, UpdatesToolSessionEventToolSessionElementUpdated):
- self._handle_element_updated(event)
- elif isinstance(event, UpdatesToolSessionEventToolSessionActionTriggered):
- self._handle_action_triggered(event)
- else:
- log.info("unhandled event %s", event)
-
- def _handle_element_updated(
- self,
- event: UpdatesToolSessionEventToolSessionElementUpdated,
- ) -> None:
- element = event.element
- update_value = self._get_element_update_value(event)
-
- if update_value is not None:
- if self._naive_update_element(
- self._instance,
- element.id,
- element.name,
- update_value,
- ):
- log.debug(
- "did not update element %s with value %s",
- element,
- update_value,
- )
- else:
- log.debug("unexpected update element %s", element)
-
- if element.id in self._update_handlers:
- log.debug("calling update handler for %s", element.name)
- self._update_handlers[element.id]()
- else:
- log.debug("no associated update handler for %s", element.name)
-
- def _get_element_update_value(
- self,
- event: UpdatesToolSessionEventToolSessionElementUpdated,
- ) -> Union[str, float, None]:
- element = event.element
- if isinstance(
- element,
- UpdatesToolSessionEventToolSessionElementUpdatedElementTextField,
- ):
- return element.text_value
-
- if isinstance(
- element,
- UpdatesToolSessionEventToolSessionElementUpdatedElementNumberField,
- ):
- return element.number_value
-
- if isinstance(
- element,
- UpdatesToolSessionEventToolSessionElementUpdatedElementHTMLElement,
- ):
- return element.html
-
- if isinstance(
- element,
- UpdatesToolSessionEventToolSessionElementUpdatedElementSliderElement,
- ):
- return element.slider_value
-
- return None
-
- def _handle_action_triggered(
- self,
- event: UpdatesToolSessionEventToolSessionActionTriggered,
- ) -> None:
- action = event.element
- if action.name in self._actions:
- self._actions[action.name]()
- else:
- log.warning("no action found for %r", action.name)
-
- def _naive_update_element(
- self,
- app_or_container: Any, # noqa: ANN401
- element_id: str,
- element_name: str,
- value: Any, # noqa: ANN401
- ) -> bool:
- element_ids_to_names: dict[str, str] = getattr(
- app_or_container,
- "__element_ids_to_names__",
- None,
- ) # type: ignore[assignment]
- if element_ids_to_names is None:
- return False
-
- if element_id in element_ids_to_names:
- instance_element_name = element_ids_to_names[element_id]
- if instance_element_name != element_name:
- raise ElementUpdateError(element_name, instance_element_name)
- app_or_container.__dict__[element_name] = value
- return True
-
- for child_name in element_ids_to_names.values():
- child = getattr(app_or_container, child_name)
- if self._naive_update_element(child, element_id, element_name, value):
- return True
-
- return False
-
-
-def _find_instance_update_handlers(instance: object) -> dict[str, MethodType]:
- element_names_to_ids: Optional[dict[str, str]] = getattr(
- instance,
- "__element_names_to_ids__",
- None,
- )
- if element_names_to_ids is None:
- return {}
-
- methods = inspect.getmembers(instance, predicate=inspect.ismethod)
- element_names_to_handlers = {
- name.removesuffix("_updated"): method
- for name, method in methods
- if name.endswith("_updated")
- }
-
- element_ids_to_handlers = {
- element_names_to_ids[element_name]: handler
- for element_name, handler in element_names_to_handlers.items()
- if element_name in element_names_to_ids
- }
-
- for element_name in element_names_to_ids:
- child = getattr(instance, element_name, None)
- element_ids_to_handlers.update(_find_instance_update_handlers(child))
-
- return element_ids_to_handlers
diff --git a/python/tests/test_collections.py b/python/tests/test_collections.py
index 0aee4dd8..79b9d4f9 100644
--- a/python/tests/test_collections.py
+++ b/python/tests/test_collections.py
@@ -4,8 +4,8 @@
from numerous import collection
from numerous._client._graphql_client import COLLECTED_OBJECTS_NUMBER, GraphQLClient
-from numerous.collection.exceptions import ParentCollectionNotFoundError
-from numerous.collection.numerous_document import NumerousDocument
+from numerous.collections.exceptions import ParentCollectionNotFoundError
+from numerous.collections.numerous_document import NumerousDocument
from numerous.generated.graphql.client import Client as GQLClient
from numerous.generated.graphql.collection_collections import CollectionCollections
from numerous.generated.graphql.collection_create import CollectionCreate
diff --git a/src/numerous/collection/collection.py b/src/numerous/collection/collection.py
new file mode 100644
index 00000000..0519ecba
--- /dev/null
+++ b/src/numerous/collection/collection.py
@@ -0,0 +1 @@
+
\ No newline at end of file