atdata 0.1.3b4__py3-none-any.whl → 0.2.2b1__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.
@@ -0,0 +1,284 @@
1
+ """Lens transformation publishing for ATProto.
2
+
3
+ This module provides classes for publishing Lens transformation records to
4
+ ATProto. Lenses are published as ``ac.foundation.dataset.lens`` records.
5
+
6
+ Note:
7
+ For security reasons, lens code is stored as references to git repositories
8
+ rather than inline code. Users must manually install and import lens
9
+ implementations.
10
+ """
11
+
12
+ from typing import Optional
13
+
14
+ from .client import AtmosphereClient
15
+ from ._types import (
16
+ AtUri,
17
+ LensRecord,
18
+ CodeReference,
19
+ LEXICON_NAMESPACE,
20
+ )
21
+
22
+ # Import for type checking only
23
+ from typing import TYPE_CHECKING
24
+ if TYPE_CHECKING:
25
+ from ..lens import Lens
26
+
27
+
28
+ class LensPublisher:
29
+ """Publishes Lens transformation records to ATProto.
30
+
31
+ This class creates lens records that reference source and target schemas
32
+ and point to the transformation code in a git repository.
33
+
34
+ Example:
35
+ ::
36
+
37
+ >>> @atdata.lens
38
+ ... def my_lens(source: SourceType) -> TargetType:
39
+ ... return TargetType(field=source.other_field)
40
+ >>>
41
+ >>> client = AtmosphereClient()
42
+ >>> client.login("handle", "password")
43
+ >>>
44
+ >>> publisher = LensPublisher(client)
45
+ >>> uri = publisher.publish(
46
+ ... name="my_lens",
47
+ ... source_schema_uri="at://did:plc:abc/ac.foundation.dataset.sampleSchema/source",
48
+ ... target_schema_uri="at://did:plc:abc/ac.foundation.dataset.sampleSchema/target",
49
+ ... code_repository="https://github.com/user/repo",
50
+ ... code_commit="abc123def456",
51
+ ... getter_path="mymodule.lenses:my_lens",
52
+ ... putter_path="mymodule.lenses:my_lens_putter",
53
+ ... )
54
+
55
+ Security Note:
56
+ Lens code is stored as references to git repositories rather than
57
+ inline code. This prevents arbitrary code execution from ATProto
58
+ records. Users must manually install and trust lens implementations.
59
+ """
60
+
61
+ def __init__(self, client: AtmosphereClient):
62
+ """Initialize the lens publisher.
63
+
64
+ Args:
65
+ client: Authenticated AtmosphereClient instance.
66
+ """
67
+ self.client = client
68
+
69
+ def publish(
70
+ self,
71
+ *,
72
+ name: str,
73
+ source_schema_uri: str,
74
+ target_schema_uri: str,
75
+ description: Optional[str] = None,
76
+ code_repository: Optional[str] = None,
77
+ code_commit: Optional[str] = None,
78
+ getter_path: Optional[str] = None,
79
+ putter_path: Optional[str] = None,
80
+ rkey: Optional[str] = None,
81
+ ) -> AtUri:
82
+ """Publish a lens transformation record to ATProto.
83
+
84
+ Args:
85
+ name: Human-readable lens name.
86
+ source_schema_uri: AT URI of the source schema.
87
+ target_schema_uri: AT URI of the target schema.
88
+ description: What this transformation does.
89
+ code_repository: Git repository URL containing the lens code.
90
+ code_commit: Git commit hash for reproducibility.
91
+ getter_path: Module path to the getter function
92
+ (e.g., 'mymodule.lenses:my_getter').
93
+ putter_path: Module path to the putter function
94
+ (e.g., 'mymodule.lenses:my_putter').
95
+ rkey: Optional explicit record key.
96
+
97
+ Returns:
98
+ The AT URI of the created lens record.
99
+
100
+ Raises:
101
+ ValueError: If code references are incomplete.
102
+ """
103
+ # Build code references if provided
104
+ getter_code: Optional[CodeReference] = None
105
+ putter_code: Optional[CodeReference] = None
106
+
107
+ if code_repository and code_commit:
108
+ if getter_path:
109
+ getter_code = CodeReference(
110
+ repository=code_repository,
111
+ commit=code_commit,
112
+ path=getter_path,
113
+ )
114
+ if putter_path:
115
+ putter_code = CodeReference(
116
+ repository=code_repository,
117
+ commit=code_commit,
118
+ path=putter_path,
119
+ )
120
+
121
+ lens_record = LensRecord(
122
+ name=name,
123
+ source_schema=source_schema_uri,
124
+ target_schema=target_schema_uri,
125
+ description=description,
126
+ getter_code=getter_code,
127
+ putter_code=putter_code,
128
+ )
129
+
130
+ return self.client.create_record(
131
+ collection=f"{LEXICON_NAMESPACE}.lens",
132
+ record=lens_record.to_record(),
133
+ rkey=rkey,
134
+ validate=False,
135
+ )
136
+
137
+ def publish_from_lens(
138
+ self,
139
+ lens_obj: "Lens",
140
+ *,
141
+ name: str,
142
+ source_schema_uri: str,
143
+ target_schema_uri: str,
144
+ code_repository: str,
145
+ code_commit: str,
146
+ description: Optional[str] = None,
147
+ rkey: Optional[str] = None,
148
+ ) -> AtUri:
149
+ """Publish a lens record from an existing Lens object.
150
+
151
+ This method extracts the getter and putter function names from
152
+ the Lens object and publishes a record referencing them.
153
+
154
+ Args:
155
+ lens_obj: The Lens object to publish.
156
+ name: Human-readable lens name.
157
+ source_schema_uri: AT URI of the source schema.
158
+ target_schema_uri: AT URI of the target schema.
159
+ code_repository: Git repository URL.
160
+ code_commit: Git commit hash.
161
+ description: What this transformation does.
162
+ rkey: Optional explicit record key.
163
+
164
+ Returns:
165
+ The AT URI of the created lens record.
166
+ """
167
+ # Extract function names from the lens
168
+ getter_name = lens_obj._getter.__name__
169
+ putter_name = lens_obj._putter.__name__
170
+
171
+ # Get module info if available
172
+ getter_module = getattr(lens_obj._getter, "__module__", "")
173
+ putter_module = getattr(lens_obj._putter, "__module__", "")
174
+
175
+ getter_path = f"{getter_module}:{getter_name}" if getter_module else getter_name
176
+ putter_path = f"{putter_module}:{putter_name}" if putter_module else putter_name
177
+
178
+ return self.publish(
179
+ name=name,
180
+ source_schema_uri=source_schema_uri,
181
+ target_schema_uri=target_schema_uri,
182
+ description=description,
183
+ code_repository=code_repository,
184
+ code_commit=code_commit,
185
+ getter_path=getter_path,
186
+ putter_path=putter_path,
187
+ rkey=rkey,
188
+ )
189
+
190
+
191
+ class LensLoader:
192
+ """Loads lens records from ATProto.
193
+
194
+ This class fetches lens transformation records. Note that actually
195
+ using a lens requires installing the referenced code and importing
196
+ it manually.
197
+
198
+ Example:
199
+ ::
200
+
201
+ >>> client = AtmosphereClient()
202
+ >>> loader = LensLoader(client)
203
+ >>>
204
+ >>> record = loader.get("at://did:plc:abc/ac.foundation.dataset.lens/xyz")
205
+ >>> print(record["name"])
206
+ >>> print(record["sourceSchema"])
207
+ >>> print(record.get("getterCode", {}).get("repository"))
208
+ """
209
+
210
+ def __init__(self, client: AtmosphereClient):
211
+ """Initialize the lens loader.
212
+
213
+ Args:
214
+ client: AtmosphereClient instance.
215
+ """
216
+ self.client = client
217
+
218
+ def get(self, uri: str | AtUri) -> dict:
219
+ """Fetch a lens record by AT URI.
220
+
221
+ Args:
222
+ uri: The AT URI of the lens record.
223
+
224
+ Returns:
225
+ The lens record as a dictionary.
226
+
227
+ Raises:
228
+ ValueError: If the record is not a lens record.
229
+ """
230
+ record = self.client.get_record(uri)
231
+
232
+ expected_type = f"{LEXICON_NAMESPACE}.lens"
233
+ if record.get("$type") != expected_type:
234
+ raise ValueError(
235
+ f"Record at {uri} is not a lens record. "
236
+ f"Expected $type='{expected_type}', got '{record.get('$type')}'"
237
+ )
238
+
239
+ return record
240
+
241
+ def list_all(
242
+ self,
243
+ repo: Optional[str] = None,
244
+ limit: int = 100,
245
+ ) -> list[dict]:
246
+ """List lens records from a repository.
247
+
248
+ Args:
249
+ repo: The DID of the repository. Defaults to authenticated user.
250
+ limit: Maximum number of records to return.
251
+
252
+ Returns:
253
+ List of lens records.
254
+ """
255
+ return self.client.list_lenses(repo=repo, limit=limit)
256
+
257
+ def find_by_schemas(
258
+ self,
259
+ source_schema_uri: str,
260
+ target_schema_uri: Optional[str] = None,
261
+ repo: Optional[str] = None,
262
+ ) -> list[dict]:
263
+ """Find lenses that transform between specific schemas.
264
+
265
+ Args:
266
+ source_schema_uri: AT URI of the source schema.
267
+ target_schema_uri: Optional AT URI of the target schema.
268
+ If not provided, returns all lenses from the source.
269
+ repo: The DID of the repository to search.
270
+
271
+ Returns:
272
+ List of matching lens records.
273
+ """
274
+ all_lenses = self.list_all(repo=repo, limit=1000)
275
+
276
+ matches = []
277
+ for lens_record in all_lenses:
278
+ if lens_record.get("sourceSchema") == source_schema_uri:
279
+ if target_schema_uri is None:
280
+ matches.append(lens_record)
281
+ elif lens_record.get("targetSchema") == target_schema_uri:
282
+ matches.append(lens_record)
283
+
284
+ return matches