framework-py 0.1.0.dev0__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 (63) hide show
  1. framework/__init__.py +250 -0
  2. framework/__main__.py +89 -0
  3. framework/_assets/hooks/pre-commit +59 -0
  4. framework/_assets/init.sql +63 -0
  5. framework/_assets/schema/layer.v1.json +605 -0
  6. framework/_assets/schema/layers.v1.json +791 -0
  7. framework/_assets/schema/spec.v1.json +604 -0
  8. framework/_log.py +29 -0
  9. framework/_paths.py +45 -0
  10. framework/_secretservice.py +69 -0
  11. framework/_time.py +11 -0
  12. framework/_wincred.py +105 -0
  13. framework/_yaml.py +90 -0
  14. framework/ai.py +204 -0
  15. framework/bootstrap.py +206 -0
  16. framework/cache.py +335 -0
  17. framework/catalog.py +193 -0
  18. framework/cloud.py +899 -0
  19. framework/config_home.py +194 -0
  20. framework/connections.py +442 -0
  21. framework/console.py +41 -0
  22. framework/create.py +579 -0
  23. framework/data.py +519 -0
  24. framework/db.py +222 -0
  25. framework/dotenv.py +79 -0
  26. framework/errors.py +39 -0
  27. framework/global_config.py +238 -0
  28. framework/hooks.py +364 -0
  29. framework/integrity.py +22 -0
  30. framework/notebooks.py +384 -0
  31. framework/outputs.py +324 -0
  32. framework/packages.py +276 -0
  33. framework/project.py +52 -0
  34. framework/py.typed +0 -0
  35. framework/quarto.py +163 -0
  36. framework/scaffold.py +269 -0
  37. framework/scratch.py +108 -0
  38. framework/security.py +646 -0
  39. framework/settings.py +439 -0
  40. framework/spec/__init__.py +0 -0
  41. framework/spec/check.py +211 -0
  42. framework/spec/merge.py +195 -0
  43. framework/spec/resolve.py +254 -0
  44. framework/storage.py +516 -0
  45. framework/templates/defaults.yml +144 -0
  46. framework/templates/gitignore/course +120 -0
  47. framework/templates/gitignore/presentation +114 -0
  48. framework/templates/gitignore/project +108 -0
  49. framework/templates/gitignore/sensitive +139 -0
  50. framework/templates/skills/framework-data/SKILL.md +47 -0
  51. framework/templates/skills/framework-outputs/SKILL.md +68 -0
  52. framework/templates/skills/framework-packages/SKILL.md +33 -0
  53. framework/templates/skills/framework-sensitive-data/SKILL.md +38 -0
  54. framework/templates/skills/framework-workflow/SKILL.md +58 -0
  55. framework/templates/stubs/notebook-default.ipynb +34 -0
  56. framework/templates/stubs/notebook-default.qmd +43 -0
  57. framework/templates/stubs/notebook-minimal.qmd +15 -0
  58. framework/templates/stubs/notebook-revealjs.qmd +34 -0
  59. framework/templates/stubs/script-default.py +18 -0
  60. framework_py-0.1.0.dev0.dist-info/METADATA +111 -0
  61. framework_py-0.1.0.dev0.dist-info/RECORD +63 -0
  62. framework_py-0.1.0.dev0.dist-info/WHEEL +4 -0
  63. framework_py-0.1.0.dev0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,604 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "https://framework.pub/schema/spec/v1.json",
4
+ "title": "Framework project spec (resolved)",
5
+ "description": "A complete project spec, produced by spec_resolve() and accepted by project_create().",
6
+ "type": "object",
7
+ "properties": {
8
+ "spec_version": {
9
+ "const": 1
10
+ },
11
+ "name": {
12
+ "type": "string",
13
+ "minLength": 1
14
+ },
15
+ "type": {
16
+ "type": "string",
17
+ "enum": [
18
+ "project",
19
+ "project_sensitive",
20
+ "course",
21
+ "presentation",
22
+ "bare"
23
+ ]
24
+ },
25
+ "language": {
26
+ "type": "string",
27
+ "enum": [
28
+ "r",
29
+ "python",
30
+ "stata"
31
+ ],
32
+ "description": "Primary language. The R package builds r, the Python package builds python, and fw builds stata (scaffolding only). A layers file with no language means r."
33
+ },
34
+ "author": {
35
+ "type": "object",
36
+ "properties": {
37
+ "name": {
38
+ "type": "string"
39
+ },
40
+ "email": {
41
+ "type": "string"
42
+ },
43
+ "affiliation": {
44
+ "type": "string"
45
+ },
46
+ "orcid": {
47
+ "type": "string"
48
+ },
49
+ "url": {
50
+ "type": "string"
51
+ },
52
+ "affiliations": {
53
+ "type": "array",
54
+ "items": {
55
+ "type": "object",
56
+ "properties": {
57
+ "name": {
58
+ "type": "string",
59
+ "minLength": 1
60
+ },
61
+ "department": {
62
+ "type": "string"
63
+ },
64
+ "city": {
65
+ "type": "string"
66
+ },
67
+ "country": {
68
+ "type": "string"
69
+ }
70
+ },
71
+ "additionalProperties": false,
72
+ "required": [
73
+ "name"
74
+ ]
75
+ }
76
+ }
77
+ },
78
+ "additionalProperties": false,
79
+ "required": [
80
+ "name",
81
+ "email",
82
+ "affiliation"
83
+ ]
84
+ },
85
+ "directories": {
86
+ "type": "object",
87
+ "additionalProperties": {
88
+ "type": "string",
89
+ "pattern": "^(?!/)(?!.*(^|/)\\.\\.(/|$))[^\\\\]+$"
90
+ },
91
+ "propertyNames": {
92
+ "pattern": "^[a-z][a-z0-9_]*$"
93
+ }
94
+ },
95
+ "extra_directories": {
96
+ "type": "object",
97
+ "additionalProperties": {
98
+ "$ref": "#/definitions/extra_directory"
99
+ },
100
+ "propertyNames": {
101
+ "pattern": "^[a-z][a-z0-9_]*$"
102
+ }
103
+ },
104
+ "render_dirs": {
105
+ "type": "object",
106
+ "additionalProperties": {
107
+ "type": "string",
108
+ "pattern": "^(?!/)(?!.*(^|/)\\.\\.(/|$))[^\\\\]+$"
109
+ },
110
+ "propertyNames": {
111
+ "pattern": "^[a-z][a-z0-9_]*$"
112
+ }
113
+ },
114
+ "packages": {
115
+ "type": "object",
116
+ "properties": {
117
+ "use_renv": {
118
+ "type": "boolean"
119
+ },
120
+ "default_packages": {
121
+ "type": "object",
122
+ "additionalProperties": {
123
+ "$ref": "#/definitions/package"
124
+ },
125
+ "description": "Package name -> spec. JSON object key order is not kept (Postgres JSONB sorts keys).",
126
+ "propertyNames": {
127
+ "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]*$"
128
+ }
129
+ }
130
+ },
131
+ "additionalProperties": false,
132
+ "required": [
133
+ "default_packages"
134
+ ]
135
+ },
136
+ "git": {
137
+ "type": "object",
138
+ "properties": {
139
+ "use_git": {
140
+ "type": "boolean"
141
+ },
142
+ "hooks": {
143
+ "type": "object",
144
+ "additionalProperties": {
145
+ "type": "boolean"
146
+ },
147
+ "propertyNames": {
148
+ "pattern": "^[a-z][a-z0-9_]*$"
149
+ }
150
+ },
151
+ "gitignore_content": {
152
+ "type": "string"
153
+ }
154
+ },
155
+ "additionalProperties": false,
156
+ "required": [
157
+ "use_git",
158
+ "hooks",
159
+ "gitignore_content"
160
+ ]
161
+ },
162
+ "ai": {
163
+ "type": "object",
164
+ "properties": {
165
+ "enabled": {
166
+ "type": "boolean"
167
+ },
168
+ "assistants": {
169
+ "type": "object",
170
+ "propertyNames": {
171
+ "enum": [
172
+ "claude",
173
+ "codex",
174
+ "copilot"
175
+ ]
176
+ },
177
+ "additionalProperties": {
178
+ "type": "boolean"
179
+ },
180
+ "description": "Assistant -> true to include. In a layer, false or null removes it."
181
+ },
182
+ "agents_md": {
183
+ "type": "string",
184
+ "enum": [
185
+ "stub",
186
+ "none"
187
+ ]
188
+ }
189
+ },
190
+ "additionalProperties": false,
191
+ "required": [
192
+ "enabled",
193
+ "assistants",
194
+ "agents_md"
195
+ ]
196
+ },
197
+ "scaffold": {
198
+ "type": "object",
199
+ "properties": {
200
+ "seed_on_scaffold": {
201
+ "type": "boolean"
202
+ },
203
+ "seed": {
204
+ "type": "string"
205
+ },
206
+ "set_theme_on_scaffold": {
207
+ "type": "boolean"
208
+ },
209
+ "ggplot_theme": {
210
+ "type": "string"
211
+ },
212
+ "source_all_functions": {
213
+ "type": "boolean"
214
+ },
215
+ "notebook_format": {
216
+ "type": "string",
217
+ "enum": [
218
+ "quarto",
219
+ "rmarkdown",
220
+ "jupyter"
221
+ ]
222
+ },
223
+ "ide": {
224
+ "type": "string",
225
+ "enum": [
226
+ "vscode",
227
+ "positron",
228
+ "rstudio",
229
+ "both",
230
+ "none"
231
+ ]
232
+ }
233
+ },
234
+ "additionalProperties": false,
235
+ "required": [
236
+ "seed_on_scaffold",
237
+ "seed"
238
+ ]
239
+ },
240
+ "quarto": {
241
+ "type": "object",
242
+ "description": "Passed through to _quarto.yml generation"
243
+ },
244
+ "connections": {
245
+ "type": "object",
246
+ "properties": {
247
+ "default_database": {
248
+ "anyOf": [
249
+ {
250
+ "type": "string"
251
+ },
252
+ {
253
+ "type": "null"
254
+ }
255
+ ]
256
+ },
257
+ "default_storage_bucket": {
258
+ "anyOf": [
259
+ {
260
+ "type": "string"
261
+ },
262
+ {
263
+ "type": "null"
264
+ }
265
+ ]
266
+ },
267
+ "databases": {
268
+ "type": "object",
269
+ "additionalProperties": {
270
+ "$ref": "#/definitions/connection"
271
+ },
272
+ "propertyNames": {
273
+ "pattern": "^[a-z][a-z0-9_]*$"
274
+ }
275
+ },
276
+ "storage_buckets": {
277
+ "type": "object",
278
+ "additionalProperties": {
279
+ "$ref": "#/definitions/connection"
280
+ },
281
+ "propertyNames": {
282
+ "pattern": "^[a-z][a-z0-9_]*$"
283
+ }
284
+ }
285
+ },
286
+ "additionalProperties": false,
287
+ "required": [
288
+ "databases",
289
+ "storage_buckets"
290
+ ]
291
+ },
292
+ "security": {
293
+ "type": "object",
294
+ "properties": {
295
+ "sensitive": {
296
+ "type": "boolean"
297
+ }
298
+ },
299
+ "additionalProperties": false,
300
+ "required": [
301
+ "sensitive"
302
+ ]
303
+ },
304
+ "env": {
305
+ "type": "object",
306
+ "additionalProperties": {
307
+ "type": "string"
308
+ },
309
+ "description": "Variables written to .env. Secret-looking names must be empty.",
310
+ "propertyNames": {
311
+ "pattern": "^[A-Za-z_][A-Za-z0-9_]*$"
312
+ }
313
+ },
314
+ "data_hooks": {
315
+ "type": "object",
316
+ "additionalProperties": {
317
+ "type": "string",
318
+ "pattern": "^[^:]+\\.(R|py)::[A-Za-z_.][A-Za-z0-9._]*$"
319
+ },
320
+ "description": "Directory key -> 'path/to/file.R::function'",
321
+ "propertyNames": {
322
+ "pattern": "^[a-z][a-z0-9_]*$"
323
+ }
324
+ },
325
+ "files": {
326
+ "type": "object",
327
+ "additionalProperties": {
328
+ "$ref": "#/definitions/file"
329
+ },
330
+ "propertyNames": {
331
+ "pattern": "^(?!/)(?!.*(^|/)\\.\\.(/|$))[^\\\\]+$"
332
+ }
333
+ },
334
+ "skills": {
335
+ "type": "object",
336
+ "additionalProperties": {
337
+ "$ref": "#/definitions/skill"
338
+ },
339
+ "propertyNames": {
340
+ "pattern": "^[a-z0-9]+(-[a-z0-9]+)*$"
341
+ }
342
+ },
343
+ "source": {
344
+ "type": "object",
345
+ "properties": {
346
+ "origin": {
347
+ "type": "string",
348
+ "enum": [
349
+ "cloud",
350
+ "cli",
351
+ "r",
352
+ "python"
353
+ ]
354
+ },
355
+ "fragments": {
356
+ "type": "array",
357
+ "items": {
358
+ "type": "object",
359
+ "properties": {
360
+ "kind": {
361
+ "type": "string",
362
+ "enum": [
363
+ "base",
364
+ "facet",
365
+ "blueprint"
366
+ ]
367
+ },
368
+ "key": {
369
+ "type": "string"
370
+ },
371
+ "version": {
372
+ "type": "integer"
373
+ }
374
+ },
375
+ "additionalProperties": false,
376
+ "required": [
377
+ "kind",
378
+ "key"
379
+ ]
380
+ }
381
+ },
382
+ "blueprint": {
383
+ "type": "object",
384
+ "properties": {
385
+ "key": {
386
+ "type": "string"
387
+ },
388
+ "version": {
389
+ "type": "integer"
390
+ }
391
+ },
392
+ "additionalProperties": false,
393
+ "required": [
394
+ "key"
395
+ ]
396
+ },
397
+ "study_plan": {
398
+ "type": "object",
399
+ "properties": {
400
+ "id": {
401
+ "type": "string"
402
+ },
403
+ "version": {
404
+ "type": "integer"
405
+ }
406
+ },
407
+ "additionalProperties": false,
408
+ "required": [
409
+ "id"
410
+ ]
411
+ },
412
+ "framework_version": {
413
+ "type": "string",
414
+ "description": "Version of the R or Python package that created the project"
415
+ },
416
+ "fw_version": {
417
+ "type": "string",
418
+ "description": "Version of fw that created the project (Stata scaffolding)"
419
+ }
420
+ },
421
+ "additionalProperties": false,
422
+ "required": [
423
+ "origin"
424
+ ]
425
+ }
426
+ },
427
+ "additionalProperties": false,
428
+ "required": [
429
+ "spec_version",
430
+ "name",
431
+ "type",
432
+ "language",
433
+ "author",
434
+ "directories",
435
+ "extra_directories",
436
+ "git",
437
+ "ai",
438
+ "security",
439
+ "env",
440
+ "data_hooks",
441
+ "files",
442
+ "skills"
443
+ ],
444
+ "allOf": [
445
+ {
446
+ "if": {
447
+ "properties": {
448
+ "language": {
449
+ "enum": [
450
+ "r",
451
+ "python"
452
+ ]
453
+ }
454
+ }
455
+ },
456
+ "then": {
457
+ "required": [
458
+ "render_dirs",
459
+ "packages",
460
+ "scaffold",
461
+ "connections"
462
+ ]
463
+ }
464
+ }
465
+ ],
466
+ "definitions": {
467
+ "package": {
468
+ "type": "object",
469
+ "properties": {
470
+ "source": {
471
+ "type": "string",
472
+ "enum": [
473
+ "cran",
474
+ "github",
475
+ "bioc",
476
+ "pypi"
477
+ ]
478
+ },
479
+ "version": {
480
+ "anyOf": [
481
+ {
482
+ "type": "string"
483
+ },
484
+ {
485
+ "type": "null"
486
+ }
487
+ ]
488
+ },
489
+ "repo": {
490
+ "anyOf": [
491
+ {
492
+ "type": "string"
493
+ },
494
+ {
495
+ "type": "null"
496
+ }
497
+ ]
498
+ },
499
+ "ref": {
500
+ "anyOf": [
501
+ {
502
+ "type": "string"
503
+ },
504
+ {
505
+ "type": "null"
506
+ }
507
+ ]
508
+ },
509
+ "auto_attach": {
510
+ "type": "boolean"
511
+ }
512
+ },
513
+ "additionalProperties": false,
514
+ "required": [
515
+ "source",
516
+ "auto_attach"
517
+ ]
518
+ },
519
+ "extra_directory": {
520
+ "type": "object",
521
+ "properties": {
522
+ "label": {
523
+ "type": "string"
524
+ },
525
+ "path": {
526
+ "type": "string",
527
+ "pattern": "^(?!/)(?!.*(^|/)\\.\\.(/|$))[^\\\\]+$"
528
+ }
529
+ },
530
+ "additionalProperties": false,
531
+ "required": [
532
+ "path"
533
+ ]
534
+ },
535
+ "file": {
536
+ "type": "object",
537
+ "properties": {
538
+ "content": {
539
+ "type": "string"
540
+ },
541
+ "template": {
542
+ "type": "boolean",
543
+ "description": "Replace {{project_name}}, {{author}}, {{author_email}}, {{affiliation}}, and {{date}}"
544
+ }
545
+ },
546
+ "additionalProperties": false,
547
+ "required": [
548
+ "content"
549
+ ]
550
+ },
551
+ "skill": {
552
+ "oneOf": [
553
+ {
554
+ "type": "object",
555
+ "properties": {
556
+ "package": {
557
+ "const": true
558
+ }
559
+ },
560
+ "additionalProperties": false,
561
+ "required": [
562
+ "package"
563
+ ]
564
+ },
565
+ {
566
+ "type": "object",
567
+ "properties": {
568
+ "content": {
569
+ "type": "string",
570
+ "minLength": 1,
571
+ "description": "Full SKILL.md text, including frontmatter"
572
+ },
573
+ "files": {
574
+ "type": "object",
575
+ "additionalProperties": {
576
+ "type": "string"
577
+ },
578
+ "propertyNames": {
579
+ "pattern": "^(?!/)(?!.*(^|/)\\.\\.(/|$))[^\\\\]+$"
580
+ }
581
+ }
582
+ },
583
+ "additionalProperties": false,
584
+ "required": [
585
+ "content"
586
+ ]
587
+ }
588
+ ]
589
+ },
590
+ "connection": {
591
+ "type": "object",
592
+ "properties": {
593
+ "driver": {
594
+ "type": "string",
595
+ "minLength": 1
596
+ }
597
+ },
598
+ "additionalProperties": true,
599
+ "required": [
600
+ "driver"
601
+ ]
602
+ }
603
+ }
604
+ }
framework/_log.py ADDED
@@ -0,0 +1,29 @@
1
+ """Framework's progress messages.
2
+
3
+ Messages go to the ``framework`` logger. A library stays quiet unless the
4
+ caller configures logging. ``scaffold()`` and project creation call
5
+ ``show_messages()`` so interactive work shows them, as R's messages do.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import logging
11
+ import sys
12
+
13
+ logger = logging.getLogger("framework")
14
+ logger.addHandler(logging.NullHandler())
15
+
16
+
17
+ def inform(message: str) -> None:
18
+ logger.info(message)
19
+
20
+
21
+ def show_messages() -> None:
22
+ """Print Framework's messages to stderr, once per process."""
23
+ if any(getattr(handler, "_framework", False) for handler in logger.handlers):
24
+ return
25
+ handler = logging.StreamHandler(sys.stderr)
26
+ handler.setFormatter(logging.Formatter("%(message)s"))
27
+ handler._framework = True # type: ignore[attr-defined]
28
+ logger.addHandler(handler)
29
+ logger.setLevel(logging.INFO)
framework/_paths.py ADDED
@@ -0,0 +1,45 @@
1
+ """Resolving project folders from the settings."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+ from typing import Any
7
+
8
+ from framework.project import project_root
9
+
10
+
11
+ def relative_to_root(path: Path, root: Path) -> str:
12
+ """``path`` relative to the project root when it is inside it, else absolute."""
13
+ resolved = path.resolve()
14
+ try:
15
+ return resolved.relative_to(root.resolve()).as_posix()
16
+ except ValueError:
17
+ return resolved.as_posix()
18
+
19
+
20
+ def in_root(path: str | Path, root: Path) -> Path:
21
+ """A settings path, which is relative to the project root unless absolute."""
22
+ candidate = Path(path).expanduser()
23
+ return candidate if candidate.is_absolute() else root / candidate
24
+
25
+
26
+ def directory(config: dict[str, Any], key: str, default: str, root: Path | None = None) -> Path:
27
+ """A folder from ``directories`` in the settings, or ``default``, under the root."""
28
+ root = root or project_root()
29
+ directories = config.get("directories")
30
+ value = directories.get(key) if isinstance(directories, dict) else None
31
+ return in_root(value if isinstance(value, str) and value else default, root)
32
+
33
+
34
+ def output_directory(config: dict[str, Any], kind: str, public: bool) -> Path:
35
+ """Where outputs of one kind (tables, figures, ...) go.
36
+
37
+ Sensitive projects split outputs into public and private folders; other
38
+ projects use one folder per kind. This follows R's ``save_*()`` functions.
39
+ """
40
+ if config.get("project_type") == "project_sensitive":
41
+ key = f"outputs_{'public' if public else 'private'}_{kind}"
42
+ else:
43
+ key = f"outputs_{kind}"
44
+ default = f"outputs/public/{kind}" if public else f"outputs/{kind}"
45
+ return directory(config, key, default)