surrealdb-orm 0.1.4__py3-none-any.whl → 0.5.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. surreal_orm/__init__.py +72 -3
  2. surreal_orm/aggregations.py +164 -0
  3. surreal_orm/auth/__init__.py +15 -0
  4. surreal_orm/auth/access.py +167 -0
  5. surreal_orm/auth/mixins.py +302 -0
  6. surreal_orm/cli/__init__.py +15 -0
  7. surreal_orm/cli/commands.py +369 -0
  8. surreal_orm/connection_manager.py +58 -18
  9. surreal_orm/fields/__init__.py +36 -0
  10. surreal_orm/fields/encrypted.py +166 -0
  11. surreal_orm/fields/relation.py +465 -0
  12. surreal_orm/migrations/__init__.py +51 -0
  13. surreal_orm/migrations/executor.py +380 -0
  14. surreal_orm/migrations/generator.py +272 -0
  15. surreal_orm/migrations/introspector.py +305 -0
  16. surreal_orm/migrations/migration.py +188 -0
  17. surreal_orm/migrations/operations.py +531 -0
  18. surreal_orm/migrations/state.py +406 -0
  19. surreal_orm/model_base.py +530 -44
  20. surreal_orm/query_set.py +609 -33
  21. surreal_orm/relations.py +645 -0
  22. surreal_orm/surreal_function.py +95 -0
  23. surreal_orm/surreal_ql.py +113 -0
  24. surreal_orm/types.py +86 -0
  25. surreal_sdk/README.md +79 -0
  26. surreal_sdk/__init__.py +151 -0
  27. surreal_sdk/connection/__init__.py +17 -0
  28. surreal_sdk/connection/base.py +516 -0
  29. surreal_sdk/connection/http.py +421 -0
  30. surreal_sdk/connection/pool.py +244 -0
  31. surreal_sdk/connection/websocket.py +519 -0
  32. surreal_sdk/exceptions.py +71 -0
  33. surreal_sdk/functions.py +607 -0
  34. surreal_sdk/protocol/__init__.py +13 -0
  35. surreal_sdk/protocol/rpc.py +218 -0
  36. surreal_sdk/py.typed +0 -0
  37. surreal_sdk/pyproject.toml +49 -0
  38. surreal_sdk/streaming/__init__.py +31 -0
  39. surreal_sdk/streaming/change_feed.py +278 -0
  40. surreal_sdk/streaming/live_query.py +265 -0
  41. surreal_sdk/streaming/live_select.py +369 -0
  42. surreal_sdk/transaction.py +386 -0
  43. surreal_sdk/types.py +346 -0
  44. surrealdb_orm-0.5.0.dist-info/METADATA +465 -0
  45. surrealdb_orm-0.5.0.dist-info/RECORD +52 -0
  46. {surrealdb_orm-0.1.4.dist-info → surrealdb_orm-0.5.0.dist-info}/WHEEL +1 -1
  47. surrealdb_orm-0.5.0.dist-info/entry_points.txt +2 -0
  48. {surrealdb_orm-0.1.4.dist-info → surrealdb_orm-0.5.0.dist-info}/licenses/LICENSE +1 -1
  49. surrealdb_orm-0.1.4.dist-info/METADATA +0 -184
  50. surrealdb_orm-0.1.4.dist-info/RECORD +0 -12
surreal_orm/__init__.py CHANGED
@@ -1,12 +1,81 @@
1
- from .model_base import BaseSurrealModel, SurrealConfigDict
1
+ """
2
+ SurrealDB ORM - A Django-style ORM for SurrealDB.
3
+
4
+ This package provides:
5
+ - Model definitions with Pydantic validation
6
+ - Query building with fluent interface
7
+ - Migration system with version control
8
+ - JWT authentication support
9
+ - CLI tools for schema management
10
+ """
11
+
2
12
  from .connection_manager import SurrealDBConnectionManager
3
- from .query_set import QuerySet
4
13
  from .enum import OrderBy
14
+ from .model_base import (
15
+ BaseSurrealModel,
16
+ SurrealConfigDict,
17
+ SurrealDbError,
18
+ get_registered_models,
19
+ )
20
+ from .query_set import QuerySet
21
+ from .types import (
22
+ EncryptionAlgorithm,
23
+ FieldType,
24
+ SchemaMode,
25
+ TableType,
26
+ )
27
+ from .fields import Encrypted
28
+ from .fields import (
29
+ ForeignKey,
30
+ ManyToMany,
31
+ Relation,
32
+ RelationInfo,
33
+ get_relation_info,
34
+ is_foreign_key,
35
+ is_graph_relation,
36
+ is_many_to_many,
37
+ is_relation_field,
38
+ )
39
+ from .auth import AuthenticatedUserMixin
40
+ from .aggregations import Aggregation, Count, Sum, Avg, Min, Max
5
41
 
6
42
  __all__ = [
43
+ # Connection
7
44
  "SurrealDBConnectionManager",
45
+ # Models
8
46
  "BaseSurrealModel",
47
+ "SurrealConfigDict",
48
+ "SurrealDbError",
49
+ "get_registered_models",
50
+ # Query
9
51
  "QuerySet",
10
52
  "OrderBy",
11
- "SurrealConfigDict",
53
+ # Aggregations
54
+ "Aggregation",
55
+ "Count",
56
+ "Sum",
57
+ "Avg",
58
+ "Min",
59
+ "Max",
60
+ # Types
61
+ "TableType",
62
+ "SchemaMode",
63
+ "FieldType",
64
+ "EncryptionAlgorithm",
65
+ # Fields
66
+ "Encrypted",
67
+ # Relations
68
+ "ForeignKey",
69
+ "ManyToMany",
70
+ "Relation",
71
+ "RelationInfo",
72
+ "get_relation_info",
73
+ "is_foreign_key",
74
+ "is_graph_relation",
75
+ "is_many_to_many",
76
+ "is_relation_field",
77
+ # Auth
78
+ "AuthenticatedUserMixin",
12
79
  ]
80
+
81
+ __version__ = "0.4.0"
@@ -0,0 +1,164 @@
1
+ """
2
+ Aggregation classes for QuerySet GROUP BY operations.
3
+
4
+ These classes are used with QuerySet.annotate() to compute aggregated values.
5
+
6
+ Example:
7
+ from surreal_orm import QuerySet
8
+ from surreal_orm.aggregations import Count, Sum, Avg
9
+
10
+ stats = await Order.objects().values("status").annotate(
11
+ count=Count(),
12
+ total=Sum("amount"),
13
+ avg_amount=Avg("amount"),
14
+ )
15
+ """
16
+
17
+ from abc import ABC, abstractmethod
18
+
19
+
20
+ class Aggregation(ABC):
21
+ """Base class for aggregation functions."""
22
+
23
+ @abstractmethod
24
+ def to_surql(self, alias: str) -> str:
25
+ """
26
+ Convert the aggregation to SurrealQL syntax.
27
+
28
+ Args:
29
+ alias: The alias for the result field.
30
+
31
+ Returns:
32
+ str: SurrealQL expression for the aggregation.
33
+ """
34
+ ...
35
+
36
+ @property
37
+ @abstractmethod
38
+ def function_name(self) -> str:
39
+ """Return the SurrealQL function name."""
40
+ ...
41
+
42
+
43
+ class Count(Aggregation):
44
+ """
45
+ Count aggregation.
46
+
47
+ Counts the number of records in each group.
48
+
49
+ Example:
50
+ Count() # Counts all records
51
+ """
52
+
53
+ def to_surql(self, alias: str) -> str:
54
+ return f"count() AS {alias}"
55
+
56
+ @property
57
+ def function_name(self) -> str:
58
+ return "count"
59
+
60
+
61
+ class Sum(Aggregation):
62
+ """
63
+ Sum aggregation.
64
+
65
+ Calculates the sum of a numeric field.
66
+
67
+ Args:
68
+ field: The field name to sum.
69
+
70
+ Example:
71
+ Sum("amount") # Sums the "amount" field
72
+ """
73
+
74
+ def __init__(self, field: str):
75
+ self.field = field
76
+
77
+ def to_surql(self, alias: str) -> str:
78
+ return f"math::sum({self.field}) AS {alias}"
79
+
80
+ @property
81
+ def function_name(self) -> str:
82
+ return "math::sum"
83
+
84
+
85
+ class Avg(Aggregation):
86
+ """
87
+ Average aggregation.
88
+
89
+ Calculates the average of a numeric field.
90
+
91
+ Args:
92
+ field: The field name to average.
93
+
94
+ Example:
95
+ Avg("age") # Averages the "age" field
96
+ """
97
+
98
+ def __init__(self, field: str):
99
+ self.field = field
100
+
101
+ def to_surql(self, alias: str) -> str:
102
+ return f"math::mean({self.field}) AS {alias}"
103
+
104
+ @property
105
+ def function_name(self) -> str:
106
+ return "math::mean"
107
+
108
+
109
+ class Min(Aggregation):
110
+ """
111
+ Minimum aggregation.
112
+
113
+ Finds the minimum value of a field.
114
+
115
+ Args:
116
+ field: The field name to find minimum of.
117
+
118
+ Example:
119
+ Min("price") # Finds minimum "price"
120
+ """
121
+
122
+ def __init__(self, field: str):
123
+ self.field = field
124
+
125
+ def to_surql(self, alias: str) -> str:
126
+ return f"math::min({self.field}) AS {alias}"
127
+
128
+ @property
129
+ def function_name(self) -> str:
130
+ return "math::min"
131
+
132
+
133
+ class Max(Aggregation):
134
+ """
135
+ Maximum aggregation.
136
+
137
+ Finds the maximum value of a field.
138
+
139
+ Args:
140
+ field: The field name to find maximum of.
141
+
142
+ Example:
143
+ Max("price") # Finds maximum "price"
144
+ """
145
+
146
+ def __init__(self, field: str):
147
+ self.field = field
148
+
149
+ def to_surql(self, alias: str) -> str:
150
+ return f"math::max({self.field}) AS {alias}"
151
+
152
+ @property
153
+ def function_name(self) -> str:
154
+ return "math::max"
155
+
156
+
157
+ __all__ = [
158
+ "Aggregation",
159
+ "Count",
160
+ "Sum",
161
+ "Avg",
162
+ "Min",
163
+ "Max",
164
+ ]
@@ -0,0 +1,15 @@
1
+ """
2
+ Authentication module for SurrealDB ORM.
3
+
4
+ Provides JWT authentication support using SurrealDB's native
5
+ DEFINE ACCESS ... TYPE RECORD feature.
6
+ """
7
+
8
+ from .access import AccessDefinition, AccessGenerator
9
+ from .mixins import AuthenticatedUserMixin
10
+
11
+ __all__ = [
12
+ "AccessDefinition",
13
+ "AccessGenerator",
14
+ "AuthenticatedUserMixin",
15
+ ]
@@ -0,0 +1,167 @@
1
+ """
2
+ Access definition generator for SurrealDB authentication.
3
+
4
+ Generates DEFINE ACCESS ... TYPE RECORD statements for user authentication
5
+ using SurrealDB's built-in JWT support.
6
+ """
7
+
8
+ from dataclasses import dataclass, field
9
+ from typing import TYPE_CHECKING
10
+
11
+ from ..types import EncryptionAlgorithm, TableType
12
+
13
+ if TYPE_CHECKING:
14
+ from ..model_base import BaseSurrealModel
15
+
16
+
17
+ @dataclass
18
+ class AccessDefinition:
19
+ """
20
+ Configuration for generating DEFINE ACCESS statements.
21
+
22
+ This class represents the authentication configuration for a USER table,
23
+ including signup/signin logic and token durations.
24
+
25
+ Attributes:
26
+ name: Access definition name (e.g., "user_auth")
27
+ table: Associated table name
28
+ identifier_field: Field used for signin (e.g., "email")
29
+ password_field: Field containing password
30
+ signup_fields: Dict of field -> expression for signup
31
+ signin_where: WHERE clause for signin validation
32
+ duration_token: JWT token lifetime (e.g., "15m")
33
+ duration_session: Session lifetime (e.g., "12h")
34
+ algorithm: Password hashing algorithm
35
+ """
36
+
37
+ name: str
38
+ table: str
39
+ identifier_field: str = "email"
40
+ password_field: str = "password"
41
+ signup_fields: dict[str, str] = field(default_factory=dict)
42
+ signin_where: str | None = None
43
+ duration_token: str = "15m"
44
+ duration_session: str = "12h"
45
+ algorithm: EncryptionAlgorithm = EncryptionAlgorithm.ARGON2
46
+
47
+ def __post_init__(self) -> None:
48
+ """Build default signin_where if not provided."""
49
+ if not self.signin_where:
50
+ self.signin_where = (
51
+ f"{self.identifier_field} = ${self.identifier_field} AND "
52
+ f"crypto::{self.algorithm}::compare({self.password_field}, ${self.password_field})"
53
+ )
54
+
55
+ def to_surreal_ql(self) -> str:
56
+ """
57
+ Generate the DEFINE ACCESS SurrealQL statement.
58
+
59
+ Returns:
60
+ Complete DEFINE ACCESS statement
61
+ """
62
+ # Build signup SET clause
63
+ signup_sets = ", ".join(f"{field_name} = {expr}" for field_name, expr in self.signup_fields.items())
64
+
65
+ return f"""DEFINE ACCESS {self.name} ON DATABASE TYPE RECORD
66
+ SIGNUP (CREATE {self.table} SET {signup_sets})
67
+ SIGNIN (SELECT * FROM {self.table} WHERE {self.signin_where})
68
+ DURATION FOR TOKEN {self.duration_token}, FOR SESSION {self.duration_session};"""
69
+
70
+ def to_remove_ql(self) -> str:
71
+ """
72
+ Generate the REMOVE ACCESS SurrealQL statement.
73
+
74
+ Returns:
75
+ REMOVE ACCESS statement
76
+ """
77
+ return f"REMOVE ACCESS {self.name} ON DATABASE;"
78
+
79
+
80
+ class AccessGenerator:
81
+ """
82
+ Generates AccessDefinition objects from User models.
83
+
84
+ This class inspects USER type models and generates the appropriate
85
+ DEFINE ACCESS configuration for authentication.
86
+ """
87
+
88
+ @staticmethod
89
+ def from_model(model: type["BaseSurrealModel"]) -> AccessDefinition | None:
90
+ """
91
+ Generate AccessDefinition from a model.
92
+
93
+ Only generates for USER type tables.
94
+
95
+ Args:
96
+ model: The model class to generate access for
97
+
98
+ Returns:
99
+ AccessDefinition if model is USER type, None otherwise
100
+ """
101
+ # Check if this is a USER table
102
+ table_type = model.get_table_type()
103
+ if table_type != TableType.USER:
104
+ return None
105
+
106
+ table_name = model.get_table_name()
107
+ identifier_field = model.get_identifier_field()
108
+ password_field = model.get_password_field()
109
+
110
+ # Get algorithm from config or default
111
+ config = getattr(model, "model_config", {})
112
+ algorithm_str = config.get("encryption_algorithm", "argon2")
113
+ try:
114
+ algorithm = EncryptionAlgorithm(algorithm_str)
115
+ except ValueError:
116
+ algorithm = EncryptionAlgorithm.ARGON2
117
+
118
+ # Build signup fields from model fields
119
+ signup_fields: dict[str, str] = {}
120
+
121
+ for field_name in model.model_fields:
122
+ if field_name == "id":
123
+ continue
124
+
125
+ if field_name == password_field:
126
+ # Password gets encrypted
127
+ signup_fields[field_name] = f"crypto::{algorithm}::generate(${field_name})"
128
+ else:
129
+ # Regular fields are passed through
130
+ signup_fields[field_name] = f"${field_name}"
131
+
132
+ # Add created_at if not in model
133
+ if "created_at" not in signup_fields:
134
+ signup_fields["created_at"] = "time::now()"
135
+
136
+ # Get durations from config
137
+ token_duration = config.get("token_duration", "15m")
138
+ session_duration = config.get("session_duration", "12h")
139
+
140
+ return AccessDefinition(
141
+ name=f"{table_name.lower()}_auth",
142
+ table=table_name,
143
+ identifier_field=identifier_field,
144
+ password_field=password_field,
145
+ signup_fields=signup_fields,
146
+ duration_token=token_duration,
147
+ duration_session=session_duration,
148
+ algorithm=algorithm,
149
+ )
150
+
151
+ @staticmethod
152
+ def generate_all(models: list[type["BaseSurrealModel"]]) -> list[AccessDefinition]:
153
+ """
154
+ Generate AccessDefinitions for all USER type models.
155
+
156
+ Args:
157
+ models: List of model classes
158
+
159
+ Returns:
160
+ List of AccessDefinition objects for USER tables
161
+ """
162
+ definitions = []
163
+ for model in models:
164
+ definition = AccessGenerator.from_model(model)
165
+ if definition:
166
+ definitions.append(definition)
167
+ return definitions