pawabase-iac 0.1.0__tar.gz

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 (123) hide show
  1. pawabase_iac-0.1.0/.gitignore +11 -0
  2. pawabase_iac-0.1.0/CHANGELOG.md +11 -0
  3. pawabase_iac-0.1.0/LICENSE +29 -0
  4. pawabase_iac-0.1.0/PKG-INFO +262 -0
  5. pawabase_iac-0.1.0/README.md +234 -0
  6. pawabase_iac-0.1.0/examples/school/.gitignore +4 -0
  7. pawabase_iac-0.1.0/examples/school/README.md +87 -0
  8. pawabase_iac-0.1.0/examples/school/infra/academics.py +138 -0
  9. pawabase_iac-0.1.0/examples/school/infra/admissions.py +40 -0
  10. pawabase_iac-0.1.0/examples/school/infra/assessment.py +183 -0
  11. pawabase_iac-0.1.0/examples/school/infra/attendance.py +81 -0
  12. pawabase_iac-0.1.0/examples/school/infra/campus.py +194 -0
  13. pawabase_iac-0.1.0/examples/school/infra/common.py +24 -0
  14. pawabase_iac-0.1.0/examples/school/infra/delivery.py +118 -0
  15. pawabase_iac-0.1.0/examples/school/infra/finance.py +114 -0
  16. pawabase_iac-0.1.0/examples/school/infra/flows_academics.py +298 -0
  17. pawabase_iac-0.1.0/examples/school/infra/flows_campus.py +253 -0
  18. pawabase_iac-0.1.0/examples/school/infra/flows_finance.py +228 -0
  19. pawabase_iac-0.1.0/examples/school/infra/flows_portal.py +75 -0
  20. pawabase_iac-0.1.0/examples/school/infra/flows_students.py +230 -0
  21. pawabase_iac-0.1.0/examples/school/infra/people.py +154 -0
  22. pawabase_iac-0.1.0/examples/school/infra/policies.py +69 -0
  23. pawabase_iac-0.1.0/examples/school/infra/roles.py +29 -0
  24. pawabase_iac-0.1.0/examples/school/pawabase.toml +4 -0
  25. pawabase_iac-0.1.0/examples/school/requirements.txt +3 -0
  26. pawabase_iac-0.1.0/examples/school/scripts/smoke_test.py +359 -0
  27. pawabase_iac-0.1.0/examples/school/scripts/trace.py +23 -0
  28. pawabase_iac-0.1.0/examples/school/tests/test_project.py +80 -0
  29. pawabase_iac-0.1.0/examples/store/infra/flows.py +86 -0
  30. pawabase_iac-0.1.0/examples/store/infra/messaging.py +26 -0
  31. pawabase_iac-0.1.0/examples/store/infra/policies.py +7 -0
  32. pawabase_iac-0.1.0/examples/store/infra/tables.py +71 -0
  33. pawabase_iac-0.1.0/examples/store/pawabase.toml +4 -0
  34. pawabase_iac-0.1.0/pawabase_iac/__init__.py +30 -0
  35. pawabase_iac-0.1.0/pawabase_iac/_version.py +1 -0
  36. pawabase_iac-0.1.0/pawabase_iac/access/__init__.py +3 -0
  37. pawabase_iac-0.1.0/pawabase_iac/access/role.py +43 -0
  38. pawabase_iac-0.1.0/pawabase_iac/api/__init__.py +3 -0
  39. pawabase_iac-0.1.0/pawabase_iac/api/route.py +59 -0
  40. pawabase_iac-0.1.0/pawabase_iac/cli/__init__.py +3 -0
  41. pawabase_iac-0.1.0/pawabase_iac/cli/__main__.py +3 -0
  42. pawabase_iac-0.1.0/pawabase_iac/cli/main.py +277 -0
  43. pawabase_iac-0.1.0/pawabase_iac/cli/starter.py +47 -0
  44. pawabase_iac-0.1.0/pawabase_iac/core/__init__.py +3 -0
  45. pawabase_iac-0.1.0/pawabase_iac/core/base.py +129 -0
  46. pawabase_iac-0.1.0/pawabase_iac/engine/__init__.py +3 -0
  47. pawabase_iac-0.1.0/pawabase_iac/engine/plan.py +164 -0
  48. pawabase_iac-0.1.0/pawabase_iac/engine/surface.py +41 -0
  49. pawabase_iac-0.1.0/pawabase_iac/environments/__init__.py +5 -0
  50. pawabase_iac-0.1.0/pawabase_iac/environments/base.py +47 -0
  51. pawabase_iac-0.1.0/pawabase_iac/environments/emulated.py +71 -0
  52. pawabase_iac-0.1.0/pawabase_iac/environments/remote.py +77 -0
  53. pawabase_iac-0.1.0/pawabase_iac/events/__init__.py +6 -0
  54. pawabase_iac-0.1.0/pawabase_iac/events/inbound.py +61 -0
  55. pawabase_iac-0.1.0/pawabase_iac/events/schedule.py +70 -0
  56. pawabase_iac-0.1.0/pawabase_iac/events/subscription.py +55 -0
  57. pawabase_iac-0.1.0/pawabase_iac/events/webhook.py +39 -0
  58. pawabase_iac-0.1.0/pawabase_iac/expr/__init__.py +6 -0
  59. pawabase_iac-0.1.0/pawabase_iac/expr/flow.py +147 -0
  60. pawabase_iac-0.1.0/pawabase_iac/expr/policy.py +218 -0
  61. pawabase_iac-0.1.0/pawabase_iac/flows/__init__.py +7 -0
  62. pawabase_iac-0.1.0/pawabase_iac/flows/block.py +71 -0
  63. pawabase_iac-0.1.0/pawabase_iac/flows/blocks/__init__.py +34 -0
  64. pawabase_iac-0.1.0/pawabase_iac/flows/blocks/auth.py +42 -0
  65. pawabase_iac-0.1.0/pawabase_iac/flows/blocks/cache.py +58 -0
  66. pawabase_iac-0.1.0/pawabase_iac/flows/blocks/control.py +130 -0
  67. pawabase_iac-0.1.0/pawabase_iac/flows/blocks/crypto.py +24 -0
  68. pawabase_iac-0.1.0/pawabase_iac/flows/blocks/db.py +34 -0
  69. pawabase_iac-0.1.0/pawabase_iac/flows/blocks/error.py +24 -0
  70. pawabase_iac-0.1.0/pawabase_iac/flows/blocks/event.py +23 -0
  71. pawabase_iac-0.1.0/pawabase_iac/flows/blocks/flow.py +23 -0
  72. pawabase_iac-0.1.0/pawabase_iac/flows/blocks/http.py +28 -0
  73. pawabase_iac-0.1.0/pawabase_iac/flows/blocks/json.py +34 -0
  74. pawabase_iac-0.1.0/pawabase_iac/flows/blocks/log.py +27 -0
  75. pawabase_iac-0.1.0/pawabase_iac/flows/blocks/mail.py +27 -0
  76. pawabase_iac-0.1.0/pawabase_iac/flows/blocks/math.py +24 -0
  77. pawabase_iac-0.1.0/pawabase_iac/flows/blocks/metric.py +24 -0
  78. pawabase_iac-0.1.0/pawabase_iac/flows/blocks/policy.py +24 -0
  79. pawabase_iac-0.1.0/pawabase_iac/flows/blocks/queue.py +25 -0
  80. pawabase_iac-0.1.0/pawabase_iac/flows/blocks/realtime.py +24 -0
  81. pawabase_iac-0.1.0/pawabase_iac/flows/blocks/resource.py +75 -0
  82. pawabase_iac-0.1.0/pawabase_iac/flows/blocks/response.py +24 -0
  83. pawabase_iac-0.1.0/pawabase_iac/flows/blocks/secret.py +23 -0
  84. pawabase_iac-0.1.0/pawabase_iac/flows/blocks/storage.py +63 -0
  85. pawabase_iac-0.1.0/pawabase_iac/flows/blocks/time.py +23 -0
  86. pawabase_iac-0.1.0/pawabase_iac/flows/blocks/transform.py +159 -0
  87. pawabase_iac-0.1.0/pawabase_iac/flows/blocks/trigger.py +116 -0
  88. pawabase_iac-0.1.0/pawabase_iac/flows/blocks/util.py +23 -0
  89. pawabase_iac-0.1.0/pawabase_iac/flows/blocks/validate.py +24 -0
  90. pawabase_iac-0.1.0/pawabase_iac/flows/blocks/webhook.py +23 -0
  91. pawabase_iac-0.1.0/pawabase_iac/flows/catalogue.py +576 -0
  92. pawabase_iac-0.1.0/pawabase_iac/flows/flow.py +132 -0
  93. pawabase_iac-0.1.0/pawabase_iac/flows/graph.py +159 -0
  94. pawabase_iac-0.1.0/pawabase_iac/kinds.py +19 -0
  95. pawabase_iac-0.1.0/pawabase_iac/mail/__init__.py +3 -0
  96. pawabase_iac-0.1.0/pawabase_iac/mail/template.py +43 -0
  97. pawabase_iac-0.1.0/pawabase_iac/policies/__init__.py +4 -0
  98. pawabase_iac-0.1.0/pawabase_iac/policies/check.py +88 -0
  99. pawabase_iac-0.1.0/pawabase_iac/policies/policy.py +25 -0
  100. pawabase_iac-0.1.0/pawabase_iac/policies/transformer.py +51 -0
  101. pawabase_iac-0.1.0/pawabase_iac/project/__init__.py +3 -0
  102. pawabase_iac-0.1.0/pawabase_iac/project/config.py +109 -0
  103. pawabase_iac-0.1.0/pawabase_iac/py.typed +0 -0
  104. pawabase_iac-0.1.0/pawabase_iac/storage/__init__.py +3 -0
  105. pawabase_iac-0.1.0/pawabase_iac/storage/bucket.py +50 -0
  106. pawabase_iac-0.1.0/pawabase_iac/tables/__init__.py +11 -0
  107. pawabase_iac-0.1.0/pawabase_iac/tables/check.py +107 -0
  108. pawabase_iac-0.1.0/pawabase_iac/tables/fields.py +170 -0
  109. pawabase_iac-0.1.0/pawabase_iac/tables/schema.py +24 -0
  110. pawabase_iac-0.1.0/pawabase_iac/tables/table.py +86 -0
  111. pawabase_iac-0.1.0/pawabase_iac/validate/__init__.py +3 -0
  112. pawabase_iac-0.1.0/pawabase_iac/validate/refs.py +117 -0
  113. pawabase_iac-0.1.0/pyproject.toml +56 -0
  114. pawabase_iac-0.1.0/scripts/generate_blocks.py +163 -0
  115. pawabase_iac-0.1.0/tests/conftest.py +10 -0
  116. pawabase_iac-0.1.0/tests/test_cli.py +52 -0
  117. pawabase_iac-0.1.0/tests/test_engine.py +113 -0
  118. pawabase_iac-0.1.0/tests/test_events.py +61 -0
  119. pawabase_iac-0.1.0/tests/test_flows.py +123 -0
  120. pawabase_iac-0.1.0/tests/test_policy.py +34 -0
  121. pawabase_iac-0.1.0/tests/test_readme.py +24 -0
  122. pawabase_iac-0.1.0/tests/test_server_conformance.py +83 -0
  123. pawabase_iac-0.1.0/tests/test_tables.py +91 -0
@@ -0,0 +1,11 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.pyc
4
+ .pytest_cache/
5
+ *.egg-info/
6
+ .env
7
+ .env.*
8
+ !.env.example
9
+ .pawabase/
10
+ dist/
11
+ build/
@@ -0,0 +1,11 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0
4
+
5
+ First release.
6
+
7
+ * Define a Pawabase backend as Python: tables (typed fields, constraints, relations, indexes), schemas, transformers, roles, policies (as Python expressions), flows, routes, subscriptions, schedules, webhooks, inbound hooks, buckets and mail templates.
8
+ * A flow builder over all 69 server flow blocks, generated from the server's registry, with every option typed and documented.
9
+ * Offline validation: each definition, template syntax, block settings, flow graphs, and the references between definitions.
10
+ * `pawabase validate | plan | deploy | emulate | init | whoami`; merge and override modes; deploys need a secret API key (a deploy key is enough); table migrations after changes; secrets only through `env()`.
11
+ * Conformance tests against the server's own request models and flow validator.
@@ -0,0 +1,29 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2026, Pawabase contributors
4
+ All rights reserved.
5
+
6
+ Redistribution and use in source and binary forms, with or without
7
+ modification, are permitted provided that the following conditions are met:
8
+
9
+ 1. Redistributions of source code must retain the above copyright notice, this
10
+ list of conditions and the following disclaimer.
11
+
12
+ 2. Redistributions in binary form must reproduce the above copyright notice,
13
+ this list of conditions and the following disclaimer in the documentation
14
+ and/or other materials provided with the distribution.
15
+
16
+ 3. Neither the name of the copyright holder nor the names of its
17
+ contributors may be used to endorse or promote products derived from
18
+ this software without specific prior written permission.
19
+
20
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,262 @@
1
+ Metadata-Version: 2.5
2
+ Name: pawabase-iac
3
+ Version: 0.1.0
4
+ Summary: Define a whole Pawabase backend as Python code, validate it, plan it, emulate it and deploy it.
5
+ Project-URL: Homepage, https://github.com/Pawabase/pawabase-iac
6
+ Project-URL: Source, https://github.com/Pawabase/pawabase-iac
7
+ Project-URL: Issues, https://github.com/Pawabase/pawabase-iac/issues
8
+ Project-URL: Pawabase, https://github.com/Pawabase/pawabase
9
+ Author: Pawabase contributors
10
+ License-Expression: BSD-3-Clause
11
+ License-File: LICENSE
12
+ Keywords: backend,cli,deploy,iac,infrastructure-as-code,pawabase,policies,workflows
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Environment :: Console
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: License :: OSI Approved :: BSD License
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Software Development :: Build Tools
22
+ Classifier: Typing :: Typed
23
+ Requires-Python: >=3.11
24
+ Requires-Dist: httpx>=0.27
25
+ Provides-Extra: test
26
+ Requires-Dist: pytest>=8; extra == 'test'
27
+ Description-Content-Type: text/markdown
28
+
29
+ # pawabase-iac
30
+
31
+ **Pawabase as code.** Define a whole [Pawabase](https://github.com/Pawabase/pawabase) backend in Python (tables, access policies, flows, routes, events, schedules, webhooks, buckets, email templates, roles), check it before anything is sent, see what a deploy would change, rehearse it against a simulated environment, and deploy it with an API key.
32
+
33
+ ```bash
34
+ pip install pawabase-iac
35
+
36
+ pawabase init # pawabase.toml, infra/app.py, .env.example
37
+ pawabase validate # offline: every definition and every reference between them
38
+ pawabase emulate # a pretend deploy: nothing real is touched, no key needed
39
+ pawabase plan # what a deploy would change in the real environment (reads only)
40
+ pawabase deploy # apply it; needs a deploy API key
41
+ ```
42
+
43
+ * **Everything is code.** A table, a policy, a flow with branches and loops, a cron schedule: all Python you can review, test and version.
44
+ * **Checked before it is sent.** Names, field types, constraints, policy conditions, flow graphs, template syntax, block settings and the references between all of them are validated offline, with the server's own rules.
45
+ * **A plan before a change.** `plan` and `deploy --dry-run` show what would be created (`+`), changed (`~`, with the fields that differ) or removed (`-`).
46
+ * **Safe by default.** Pushing needs a secret API key; `merge` mode never deletes what your code does not mention; production asks first; secrets never sit in the code.
47
+
48
+ ## Contents
49
+
50
+ [Quick start](#quick-start) · [Defining things](#defining-things) · [Policies](#policies) · [Flows](#flows) · [Commands](#commands) · [Deploying](#deploying) · [Emulate](#emulate) · [Using it in CI](#using-it-in-ci) · [Examples](#examples) · [Development](#development) · [Limits](#limits)
51
+
52
+ ## Quick start
53
+
54
+ ```bash
55
+ mkdir shop && cd shop
56
+ pawabase init
57
+ ```
58
+
59
+ `pawabase.toml` says where to deploy (the key does not live here):
60
+
61
+ ```toml
62
+ url = "http://localhost:8080" # your Pawabase gateway
63
+ environment = "development" # the environment to deploy to
64
+ entry = "infra" # a file or a folder of .py files holding your definitions
65
+ ```
66
+
67
+ `infra/app.py` is a small working backend. Make the API key (Studio → API keys → New key → **Secret**, preset **Deploy and configure**) and put it in `.env`:
68
+
69
+ ```bash
70
+ echo 'PAWABASE_API_KEY=pb_sk_...' >> .env
71
+ pawabase validate && pawabase emulate && pawabase deploy
72
+ ```
73
+
74
+ Edit a definition and run `pawabase plan` to see exactly what changes before you deploy it.
75
+
76
+ ## Defining things
77
+
78
+ Definitions register themselves as they are made. Files in your `entry` folder can import each other (`from infra.policies import mine`), and each file runs once. Pass a definition wherever a name is expected (`resource=todos`, `handler=summary`, `run=nightly`, `operations=crud(mine)`) and it is replaced by its name, so a rename follows and `validate` can see the dependency.
79
+
80
+ ```python
81
+ from pawabase_iac import (
82
+ Allow, Array, BelongsTo, Boolean, Desc, Email, Index, Integer, P, Policy, RateLimit, Role, String, Table, Where, crud,
83
+ )
84
+
85
+ Role("teacher", permissions=["grades.write"], description="Teaching staff")
86
+
87
+ mine = Policy("mine", P.owner("user_id"))
88
+ staff = Policy("staff", P.role("admin") | P.role("teacher"))
89
+
90
+ students = Table(
91
+ "students",
92
+ fields=[
93
+ String("user_id", unique=True),
94
+ String("admission_number", required=True, unique=True, max_length=20),
95
+ Email("email"),
96
+ Integer("age", minimum=3, maximum=25),
97
+ String("status", enum=["active", "graduated", "withdrawn"], default="active"),
98
+ Array("guardian_ids", of=String("guardian_id")),
99
+ ],
100
+ operations=crud(staff, get=Allow(mine.condition | staff.condition), delete=Allow(P.role("admin"))),
101
+ indexes=[Index("status", Desc("age"), where=[Where("status", "!=", "withdrawn")])],
102
+ rate_limit=RateLimit(120, 60),
103
+ realtime=True,
104
+ )
105
+ ```
106
+
107
+ | Kind | Class | What you set |
108
+ |---|---|---|
109
+ | Table | `Table` (also `Resource`) | typed fields, operations with a policy each, relations (`BelongsTo`, `HasMany`), indexes (`Index`, `Desc`, `Where`), rate limit, caching, owner field, realtime, event capture |
110
+ | Fields | `String`, `Text`, `Integer`, `Number`, `Boolean`, `DateTime`, `Date`, `UUID`, `ULID`, `Email`, `URL`, `JSON`, `Array`, `Object`, `Ref` | `required`, `nullable`, `default`, `unique`, `indexed`, `enum`, `min_length`/`max_length`, `minimum`/`maximum`, `pattern`, `read_only`, `write_only`, nested fields and items |
111
+ | Shapes | `Schema`, `Transformer` | reusable data shapes; omit / pick / set / rename / case on the way out |
112
+ | Access | `Role`, `Policy` | roles and their permission names; policies as Python expressions |
113
+ | Logic | `Flow`, `Route` | a graph of blocks; a custom HTTP endpoint that runs a flow |
114
+ | Events | `Subscription`, `Schedule`, `Webhook`, `InboundHook` | react to events, run on cron or an interval, call out, be called (signed) |
115
+ | Files | `Bucket` | per-action access policies, accepted types, size, signed URLs, image transforms, caching |
116
+ | Email | `MailTemplate` | subject / html / text with `{{ variables }}`, status, categories |
117
+
118
+ Secrets are never written in code: `Webhook(..., secret=env("ERP_SECRET"))` reads the variable when you deploy and refuses to deploy if it is unset.
119
+
120
+ ## Policies
121
+
122
+ ```python
123
+ from pawabase_iac import P, Policy, all_of, any_of
124
+
125
+ Policy("owner_or_staff", P.owner("user_id") | P.role("admin"))
126
+ Policy("paid_and_verified", P.authenticated() & (P.auth.plan != "free") & P.auth.email_verified.truthy())
127
+ Policy("family", (P.record.student_user_id == P.auth.user_id) | P.record.guardian_ids.contains(P.auth.user_id))
128
+ Policy("not_suspended", ~P.record.suspended.truthy())
129
+ ```
130
+
131
+ `P.auth`, `P.record`, `P.input`, `P.request`, `P.env` and `P.credential` are paths into what a policy can see. Comparing a path builds a condition (`==`, `!=`, `>`, `>=`, `<`, `<=`, `.in_()`, `.not_in()`, `.contains()`, `.startswith()`, `.endswith()`, `.matches()`, `.exists()`, `.truthy()`, `.empty()`); `&`, `|` and `~` combine them. The shorthand checks are `P.authenticated()`, `P.role()`, `P.permission()`, `P.owner()`, `P.service()`, `P.kind()`, `P.scope()` and `P.aal()`. A condition has no truth value in Python, so `and` / `or` / `not` raise instead of silently dropping half a policy.
132
+
133
+ ## Flows
134
+
135
+ ```python
136
+ from pawabase_iac import Flow, P, blocks, ctx
137
+
138
+ invoice = Flow("issue_invoice", description="Invoice a student")
139
+ start = invoice.add(blocks.trigger.http(method="POST", policy="finance_staff").named("start"))
140
+ student = invoice.add(blocks.resource.get(resource="students", id=ctx.input.student_id).named("student"))
141
+ missing = invoice.add(blocks.error.raise_(status=404, message="No such student").named("missing"))
142
+ fees = invoice.add(blocks.resource.list(resource="fee_structures", filters={"grade": ctx.steps.student.output.grade}, limit=1).named("fees"))
143
+ total = invoice.add(blocks.transform.summarize(value=ctx.steps.fees.output.data[0].items, operation="sum", field="amount").named("total"))
144
+ big = invoice.add(blocks.control.if_(P.steps.total.output > 100000).named("big"))
145
+ review = invoice.add(blocks.event.emit(event="invoice.needs_review", payload={"total": ctx.steps.total.output}).named("review"))
146
+ done = invoice.add(blocks.response.return_(status=201, body={"total": ctx.steps.total.output}).named("done"))
147
+
148
+ start >> student
149
+ student.missing >> missing
150
+ student >> fees >> total >> big
151
+ big.true >> review >> done
152
+ big.false >> done
153
+ ```
154
+
155
+ `a >> b` follows the `next` handle; `a.true >> b`, `a.error >> b` or `a.on("done") >> b` follow others. `flow.chain(a, b, c)` adds and links in one go; `flow["id"]` returns a node you named with `.named("id")`.
156
+
157
+ All **69 flow blocks** the server knows are available as typed functions, one module per family, every option documented with its default, limits and allowed values: `auth` · `cache` · `control` (if, switch, foreach, while, retry, timeout, set, merge, delay, stop) · `crypto` · `db` · `error` · `event` · `flow` · `http` · `json` · `log` · `mail` · `math` · `metric` · `policy` · `queue` · `realtime` · `resource` · `response` · `secret` · `storage` · `time` · `transform` (map, filter, sort, group, unique, flatten, slice, batch, pick, rename, summarize, template) · `trigger` (http, event, schedule, webhook, resource, job, realtime, manual) · `util` · `validate` · `webhook`.
158
+
159
+ ### The rules the server enforces (and `validate` checks)
160
+
161
+ * **Templates are lookups.** `{{ steps.load.output.id }}` and the filters `default`, `upper`, `lower`, `length`, `int`, `float`, `str`, `bool`, `first`, `last`, `keys`, `json`; nothing else. No arithmetic, no comparison, no path after a filter. Compute with `blocks.math.calculate`; decide with `blocks.control.if_(P.steps.x.output.n > 3)`. A value that is exactly one template keeps its type (a list stays a list).
162
+ * **Conditions use `P`** (`control.if_`, `control.while_`, `transform.filter`): `P.input.x`, `P.steps.<id>.output.y`, `P.vars.z`, `P.item.w`.
163
+ * **What `input` holds** depends on how the flow started: an HTTP flow gets the body (`POST /flows/v1/<name>` for a trigger with no `path`) or `{params, query, body, headers}` (a `Route`); an event flow gets `{"event": {name, payload}}` (a table's changes are `<table>.created|updated|deleted` with the row at `payload.record`); a schedule gets `{scheduled_at}` or its payload; an inbound hook gets `{hook, headers, body}`.
164
+ * **Loops.** `ctx.item` and `ctx.index` are the current element and a nested loop shadows the outer `item`: keep what you need with `blocks.control.set(values={...})` and read it from `ctx.vars`. A loop's output is the list of its body's last outputs.
165
+ * **`validate.schema` is strict**: declare every optional input as well, or it is refused.
166
+ * **Debug a deployed flow** with `POST /platform/v1/envs/<env>/flows/<name>/run {"input": {...}}`: it returns the result, or a step-by-step trace.
167
+
168
+ ## Commands
169
+
170
+ | Command | |
171
+ |---|---|
172
+ | `pawabase init` | start a project here |
173
+ | `pawabase validate` | check every definition and reference; contacts nothing |
174
+ | `pawabase plan` | compare your code with the environment; writes nothing |
175
+ | `pawabase deploy` | apply the plan (`--dry-run`, `--yes`, `--mode`, `--force-secrets`, `--no-migrate`) |
176
+ | `pawabase emulate` | rehearse a deploy against a simulated environment (`--reset`) |
177
+ | `pawabase whoami` | show the settings and test the API key |
178
+
179
+ Shared options: `-e/--env`, `--url`, `--entry`, `--json`. Exit code 0 on success, 1 on a failure or a refusal, 2 when `--yes` was needed but not given.
180
+
181
+ ## Deploying
182
+
183
+ **A key is required to push.** `deploy` refuses without `PAWABASE_API_KEY` (in `.env` or the environment), and accepts only a *secret* key (`pb_sk…`). Use a **deploy key**: Studio → API keys → New key → Secret → preset *Deploy and configure*. It may change tables, policies, routes, flows, mail templates, roles, buckets, webhooks, hooks, subscriptions and schedules, and nothing else: not users, not API keys, not the database console, not the environment itself. The gateway refuses every other request from it.
184
+
185
+ **Modes.**
186
+
187
+ * `--mode merge` (default) creates and changes what your code defines and leaves everything else alone. Anything in the environment that your code does not mention is shown as `?` in the plan.
188
+ * `--mode override` makes the environment match your code exactly and **deletes the rest**. It asks first (or needs `--yes`).
189
+
190
+ **What happens.** Everything is validated first; if an `env("…")` variable is unset nothing is sent. Definitions are applied in dependency order (roles, schemas, transformers, policies, tables, flows, buckets, templates, routes, subscriptions, webhooks, hooks, schedules). After a table is created or changed its table is migrated: columns are created or **added**, never dropped. The deploy stops at the first refusal and tells you how many later changes were not attempted; what was applied stays applied, so fix it and deploy again. Production environments (`production`, `prod`, `live`) need `--yes` when not interactive.
191
+
192
+ **Secrets.** `Webhook` and `InboundHook` secrets are write-only: sent when the definition is created or otherwise changes, never compared. Rotate one with `--force-secrets`.
193
+
194
+ **How it compares.** The server fills in defaults and ids, so the comparison asks only whether what you wrote is present in what the server returns (an empty value matches a missing one). Fields the server derives when unset (a table's `table` name) are not compared.
195
+
196
+ ## Emulate
197
+
198
+ `pawabase emulate` runs the same validation and planning against a **simulated** environment kept in `.pawabase/emulator-<env>.json`. It never touches a server and needs no key. It prints the plan, applies it to the pretend environment, and shows what the result would serve: every REST endpoint with its policy, custom routes, hooks, schedules, webhooks, subscriptions and buckets. Run it again after editing to see only what changed; `--reset` starts empty; `--mode override` shows what a destructive deploy would remove.
199
+
200
+ ## Using it in CI
201
+
202
+ ```yaml
203
+ - run: pip install pawabase-iac
204
+ - run: pawabase validate # on every pull request
205
+ - run: pawabase plan # show the diff in the log
206
+ env: { PAWABASE_URL: ${{ vars.PAWABASE_URL }}, PAWABASE_API_KEY: ${{ secrets.PAWABASE_DEPLOY_KEY }} }
207
+ - run: pawabase deploy --yes # on merge to main
208
+ env: { PAWABASE_URL: ${{ vars.PAWABASE_URL }}, PAWABASE_API_KEY: ${{ secrets.PAWABASE_DEPLOY_KEY }}, ERP_SECRET: ${{ secrets.ERP_SECRET }} }
209
+ ```
210
+
211
+ `PAWABASE_URL` and `PAWABASE_ENV` override `pawabase.toml`.
212
+
213
+ ## Examples
214
+
215
+ * [`examples/store`](examples/store): a small shop (tables, a checkout flow with a loop, retry and error paths, a receipt flow, switch-based ticket routing, subscriptions, a webhook, a signed inbound hook, buckets, a mail template).
216
+ * [`examples/school`](examples/school): a complete **school management system**: 11 roles, 22 policies, 43 tables, 25 flows, routes, subscriptions, schedules, signed payment webhooks, buckets and email templates (136 definitions) with a smoke test that runs the real flows against a deployed environment and its own offline tests.
217
+
218
+ ## Development
219
+
220
+ ```bash
221
+ pip install -e '.[test]'
222
+ pytest # the library, offline
223
+ ```
224
+
225
+ The conformance tests compare every definition's request body with the server's own request models, run flows through the server's flow validator and check the generated block catalogue against the running server's. They run where `pawabase_server` is importable:
226
+
227
+ ```bash
228
+ cd ../pawabase && PYTHONPATH=../pawabase-iac uv run python -m pytest ../pawabase-iac/tests/test_server_conformance.py
229
+ ```
230
+
231
+ Regenerate the flow blocks when the server gains some:
232
+
233
+ ```bash
234
+ cd ../pawabase && uv run python ../pawabase-iac/scripts/generate_blocks.py
235
+ ```
236
+
237
+ ```
238
+ pawabase_iac/
239
+ core/ base Definition, env(), the registry
240
+ expr/ P (policy conditions) and ctx (flow templates)
241
+ tables/ fields, Table, Schema, indexes, relations
242
+ policies/ Policy, Transformer, condition checks
243
+ access/ Role
244
+ flows/ Flow builder, graph checks, layout, catalogue.py and blocks/ (generated)
245
+ api/ Route
246
+ events/ Subscription, Schedule, Webhook, InboundHook
247
+ storage/ Bucket
248
+ mail/ MailTemplate
249
+ validate/ cross-definition reference checks
250
+ engine/ plan, apply, diff, what-it-serves
251
+ environments/ Remote (the Platform API) and Emulated (in memory)
252
+ project/ pawabase.toml, .env, importing your definitions
253
+ cli/ the pawabase command
254
+ ```
255
+
256
+ ## Limits
257
+
258
+ Environment-level settings are not managed from code yet: auth providers and settings, secrets, API keys, firewall rules, domains, alarms, dashboards, backup schedules, and mail and storage connection settings. Users are not managed either (give people roles in Accounts). The twelve kinds above are.
259
+
260
+ ## License
261
+
262
+ BSD 3-Clause. See [LICENSE](LICENSE).
@@ -0,0 +1,234 @@
1
+ # pawabase-iac
2
+
3
+ **Pawabase as code.** Define a whole [Pawabase](https://github.com/Pawabase/pawabase) backend in Python (tables, access policies, flows, routes, events, schedules, webhooks, buckets, email templates, roles), check it before anything is sent, see what a deploy would change, rehearse it against a simulated environment, and deploy it with an API key.
4
+
5
+ ```bash
6
+ pip install pawabase-iac
7
+
8
+ pawabase init # pawabase.toml, infra/app.py, .env.example
9
+ pawabase validate # offline: every definition and every reference between them
10
+ pawabase emulate # a pretend deploy: nothing real is touched, no key needed
11
+ pawabase plan # what a deploy would change in the real environment (reads only)
12
+ pawabase deploy # apply it; needs a deploy API key
13
+ ```
14
+
15
+ * **Everything is code.** A table, a policy, a flow with branches and loops, a cron schedule: all Python you can review, test and version.
16
+ * **Checked before it is sent.** Names, field types, constraints, policy conditions, flow graphs, template syntax, block settings and the references between all of them are validated offline, with the server's own rules.
17
+ * **A plan before a change.** `plan` and `deploy --dry-run` show what would be created (`+`), changed (`~`, with the fields that differ) or removed (`-`).
18
+ * **Safe by default.** Pushing needs a secret API key; `merge` mode never deletes what your code does not mention; production asks first; secrets never sit in the code.
19
+
20
+ ## Contents
21
+
22
+ [Quick start](#quick-start) · [Defining things](#defining-things) · [Policies](#policies) · [Flows](#flows) · [Commands](#commands) · [Deploying](#deploying) · [Emulate](#emulate) · [Using it in CI](#using-it-in-ci) · [Examples](#examples) · [Development](#development) · [Limits](#limits)
23
+
24
+ ## Quick start
25
+
26
+ ```bash
27
+ mkdir shop && cd shop
28
+ pawabase init
29
+ ```
30
+
31
+ `pawabase.toml` says where to deploy (the key does not live here):
32
+
33
+ ```toml
34
+ url = "http://localhost:8080" # your Pawabase gateway
35
+ environment = "development" # the environment to deploy to
36
+ entry = "infra" # a file or a folder of .py files holding your definitions
37
+ ```
38
+
39
+ `infra/app.py` is a small working backend. Make the API key (Studio → API keys → New key → **Secret**, preset **Deploy and configure**) and put it in `.env`:
40
+
41
+ ```bash
42
+ echo 'PAWABASE_API_KEY=pb_sk_...' >> .env
43
+ pawabase validate && pawabase emulate && pawabase deploy
44
+ ```
45
+
46
+ Edit a definition and run `pawabase plan` to see exactly what changes before you deploy it.
47
+
48
+ ## Defining things
49
+
50
+ Definitions register themselves as they are made. Files in your `entry` folder can import each other (`from infra.policies import mine`), and each file runs once. Pass a definition wherever a name is expected (`resource=todos`, `handler=summary`, `run=nightly`, `operations=crud(mine)`) and it is replaced by its name, so a rename follows and `validate` can see the dependency.
51
+
52
+ ```python
53
+ from pawabase_iac import (
54
+ Allow, Array, BelongsTo, Boolean, Desc, Email, Index, Integer, P, Policy, RateLimit, Role, String, Table, Where, crud,
55
+ )
56
+
57
+ Role("teacher", permissions=["grades.write"], description="Teaching staff")
58
+
59
+ mine = Policy("mine", P.owner("user_id"))
60
+ staff = Policy("staff", P.role("admin") | P.role("teacher"))
61
+
62
+ students = Table(
63
+ "students",
64
+ fields=[
65
+ String("user_id", unique=True),
66
+ String("admission_number", required=True, unique=True, max_length=20),
67
+ Email("email"),
68
+ Integer("age", minimum=3, maximum=25),
69
+ String("status", enum=["active", "graduated", "withdrawn"], default="active"),
70
+ Array("guardian_ids", of=String("guardian_id")),
71
+ ],
72
+ operations=crud(staff, get=Allow(mine.condition | staff.condition), delete=Allow(P.role("admin"))),
73
+ indexes=[Index("status", Desc("age"), where=[Where("status", "!=", "withdrawn")])],
74
+ rate_limit=RateLimit(120, 60),
75
+ realtime=True,
76
+ )
77
+ ```
78
+
79
+ | Kind | Class | What you set |
80
+ |---|---|---|
81
+ | Table | `Table` (also `Resource`) | typed fields, operations with a policy each, relations (`BelongsTo`, `HasMany`), indexes (`Index`, `Desc`, `Where`), rate limit, caching, owner field, realtime, event capture |
82
+ | Fields | `String`, `Text`, `Integer`, `Number`, `Boolean`, `DateTime`, `Date`, `UUID`, `ULID`, `Email`, `URL`, `JSON`, `Array`, `Object`, `Ref` | `required`, `nullable`, `default`, `unique`, `indexed`, `enum`, `min_length`/`max_length`, `minimum`/`maximum`, `pattern`, `read_only`, `write_only`, nested fields and items |
83
+ | Shapes | `Schema`, `Transformer` | reusable data shapes; omit / pick / set / rename / case on the way out |
84
+ | Access | `Role`, `Policy` | roles and their permission names; policies as Python expressions |
85
+ | Logic | `Flow`, `Route` | a graph of blocks; a custom HTTP endpoint that runs a flow |
86
+ | Events | `Subscription`, `Schedule`, `Webhook`, `InboundHook` | react to events, run on cron or an interval, call out, be called (signed) |
87
+ | Files | `Bucket` | per-action access policies, accepted types, size, signed URLs, image transforms, caching |
88
+ | Email | `MailTemplate` | subject / html / text with `{{ variables }}`, status, categories |
89
+
90
+ Secrets are never written in code: `Webhook(..., secret=env("ERP_SECRET"))` reads the variable when you deploy and refuses to deploy if it is unset.
91
+
92
+ ## Policies
93
+
94
+ ```python
95
+ from pawabase_iac import P, Policy, all_of, any_of
96
+
97
+ Policy("owner_or_staff", P.owner("user_id") | P.role("admin"))
98
+ Policy("paid_and_verified", P.authenticated() & (P.auth.plan != "free") & P.auth.email_verified.truthy())
99
+ Policy("family", (P.record.student_user_id == P.auth.user_id) | P.record.guardian_ids.contains(P.auth.user_id))
100
+ Policy("not_suspended", ~P.record.suspended.truthy())
101
+ ```
102
+
103
+ `P.auth`, `P.record`, `P.input`, `P.request`, `P.env` and `P.credential` are paths into what a policy can see. Comparing a path builds a condition (`==`, `!=`, `>`, `>=`, `<`, `<=`, `.in_()`, `.not_in()`, `.contains()`, `.startswith()`, `.endswith()`, `.matches()`, `.exists()`, `.truthy()`, `.empty()`); `&`, `|` and `~` combine them. The shorthand checks are `P.authenticated()`, `P.role()`, `P.permission()`, `P.owner()`, `P.service()`, `P.kind()`, `P.scope()` and `P.aal()`. A condition has no truth value in Python, so `and` / `or` / `not` raise instead of silently dropping half a policy.
104
+
105
+ ## Flows
106
+
107
+ ```python
108
+ from pawabase_iac import Flow, P, blocks, ctx
109
+
110
+ invoice = Flow("issue_invoice", description="Invoice a student")
111
+ start = invoice.add(blocks.trigger.http(method="POST", policy="finance_staff").named("start"))
112
+ student = invoice.add(blocks.resource.get(resource="students", id=ctx.input.student_id).named("student"))
113
+ missing = invoice.add(blocks.error.raise_(status=404, message="No such student").named("missing"))
114
+ fees = invoice.add(blocks.resource.list(resource="fee_structures", filters={"grade": ctx.steps.student.output.grade}, limit=1).named("fees"))
115
+ total = invoice.add(blocks.transform.summarize(value=ctx.steps.fees.output.data[0].items, operation="sum", field="amount").named("total"))
116
+ big = invoice.add(blocks.control.if_(P.steps.total.output > 100000).named("big"))
117
+ review = invoice.add(blocks.event.emit(event="invoice.needs_review", payload={"total": ctx.steps.total.output}).named("review"))
118
+ done = invoice.add(blocks.response.return_(status=201, body={"total": ctx.steps.total.output}).named("done"))
119
+
120
+ start >> student
121
+ student.missing >> missing
122
+ student >> fees >> total >> big
123
+ big.true >> review >> done
124
+ big.false >> done
125
+ ```
126
+
127
+ `a >> b` follows the `next` handle; `a.true >> b`, `a.error >> b` or `a.on("done") >> b` follow others. `flow.chain(a, b, c)` adds and links in one go; `flow["id"]` returns a node you named with `.named("id")`.
128
+
129
+ All **69 flow blocks** the server knows are available as typed functions, one module per family, every option documented with its default, limits and allowed values: `auth` · `cache` · `control` (if, switch, foreach, while, retry, timeout, set, merge, delay, stop) · `crypto` · `db` · `error` · `event` · `flow` · `http` · `json` · `log` · `mail` · `math` · `metric` · `policy` · `queue` · `realtime` · `resource` · `response` · `secret` · `storage` · `time` · `transform` (map, filter, sort, group, unique, flatten, slice, batch, pick, rename, summarize, template) · `trigger` (http, event, schedule, webhook, resource, job, realtime, manual) · `util` · `validate` · `webhook`.
130
+
131
+ ### The rules the server enforces (and `validate` checks)
132
+
133
+ * **Templates are lookups.** `{{ steps.load.output.id }}` and the filters `default`, `upper`, `lower`, `length`, `int`, `float`, `str`, `bool`, `first`, `last`, `keys`, `json`; nothing else. No arithmetic, no comparison, no path after a filter. Compute with `blocks.math.calculate`; decide with `blocks.control.if_(P.steps.x.output.n > 3)`. A value that is exactly one template keeps its type (a list stays a list).
134
+ * **Conditions use `P`** (`control.if_`, `control.while_`, `transform.filter`): `P.input.x`, `P.steps.<id>.output.y`, `P.vars.z`, `P.item.w`.
135
+ * **What `input` holds** depends on how the flow started: an HTTP flow gets the body (`POST /flows/v1/<name>` for a trigger with no `path`) or `{params, query, body, headers}` (a `Route`); an event flow gets `{"event": {name, payload}}` (a table's changes are `<table>.created|updated|deleted` with the row at `payload.record`); a schedule gets `{scheduled_at}` or its payload; an inbound hook gets `{hook, headers, body}`.
136
+ * **Loops.** `ctx.item` and `ctx.index` are the current element and a nested loop shadows the outer `item`: keep what you need with `blocks.control.set(values={...})` and read it from `ctx.vars`. A loop's output is the list of its body's last outputs.
137
+ * **`validate.schema` is strict**: declare every optional input as well, or it is refused.
138
+ * **Debug a deployed flow** with `POST /platform/v1/envs/<env>/flows/<name>/run {"input": {...}}`: it returns the result, or a step-by-step trace.
139
+
140
+ ## Commands
141
+
142
+ | Command | |
143
+ |---|---|
144
+ | `pawabase init` | start a project here |
145
+ | `pawabase validate` | check every definition and reference; contacts nothing |
146
+ | `pawabase plan` | compare your code with the environment; writes nothing |
147
+ | `pawabase deploy` | apply the plan (`--dry-run`, `--yes`, `--mode`, `--force-secrets`, `--no-migrate`) |
148
+ | `pawabase emulate` | rehearse a deploy against a simulated environment (`--reset`) |
149
+ | `pawabase whoami` | show the settings and test the API key |
150
+
151
+ Shared options: `-e/--env`, `--url`, `--entry`, `--json`. Exit code 0 on success, 1 on a failure or a refusal, 2 when `--yes` was needed but not given.
152
+
153
+ ## Deploying
154
+
155
+ **A key is required to push.** `deploy` refuses without `PAWABASE_API_KEY` (in `.env` or the environment), and accepts only a *secret* key (`pb_sk…`). Use a **deploy key**: Studio → API keys → New key → Secret → preset *Deploy and configure*. It may change tables, policies, routes, flows, mail templates, roles, buckets, webhooks, hooks, subscriptions and schedules, and nothing else: not users, not API keys, not the database console, not the environment itself. The gateway refuses every other request from it.
156
+
157
+ **Modes.**
158
+
159
+ * `--mode merge` (default) creates and changes what your code defines and leaves everything else alone. Anything in the environment that your code does not mention is shown as `?` in the plan.
160
+ * `--mode override` makes the environment match your code exactly and **deletes the rest**. It asks first (or needs `--yes`).
161
+
162
+ **What happens.** Everything is validated first; if an `env("…")` variable is unset nothing is sent. Definitions are applied in dependency order (roles, schemas, transformers, policies, tables, flows, buckets, templates, routes, subscriptions, webhooks, hooks, schedules). After a table is created or changed its table is migrated: columns are created or **added**, never dropped. The deploy stops at the first refusal and tells you how many later changes were not attempted; what was applied stays applied, so fix it and deploy again. Production environments (`production`, `prod`, `live`) need `--yes` when not interactive.
163
+
164
+ **Secrets.** `Webhook` and `InboundHook` secrets are write-only: sent when the definition is created or otherwise changes, never compared. Rotate one with `--force-secrets`.
165
+
166
+ **How it compares.** The server fills in defaults and ids, so the comparison asks only whether what you wrote is present in what the server returns (an empty value matches a missing one). Fields the server derives when unset (a table's `table` name) are not compared.
167
+
168
+ ## Emulate
169
+
170
+ `pawabase emulate` runs the same validation and planning against a **simulated** environment kept in `.pawabase/emulator-<env>.json`. It never touches a server and needs no key. It prints the plan, applies it to the pretend environment, and shows what the result would serve: every REST endpoint with its policy, custom routes, hooks, schedules, webhooks, subscriptions and buckets. Run it again after editing to see only what changed; `--reset` starts empty; `--mode override` shows what a destructive deploy would remove.
171
+
172
+ ## Using it in CI
173
+
174
+ ```yaml
175
+ - run: pip install pawabase-iac
176
+ - run: pawabase validate # on every pull request
177
+ - run: pawabase plan # show the diff in the log
178
+ env: { PAWABASE_URL: ${{ vars.PAWABASE_URL }}, PAWABASE_API_KEY: ${{ secrets.PAWABASE_DEPLOY_KEY }} }
179
+ - run: pawabase deploy --yes # on merge to main
180
+ env: { PAWABASE_URL: ${{ vars.PAWABASE_URL }}, PAWABASE_API_KEY: ${{ secrets.PAWABASE_DEPLOY_KEY }}, ERP_SECRET: ${{ secrets.ERP_SECRET }} }
181
+ ```
182
+
183
+ `PAWABASE_URL` and `PAWABASE_ENV` override `pawabase.toml`.
184
+
185
+ ## Examples
186
+
187
+ * [`examples/store`](examples/store): a small shop (tables, a checkout flow with a loop, retry and error paths, a receipt flow, switch-based ticket routing, subscriptions, a webhook, a signed inbound hook, buckets, a mail template).
188
+ * [`examples/school`](examples/school): a complete **school management system**: 11 roles, 22 policies, 43 tables, 25 flows, routes, subscriptions, schedules, signed payment webhooks, buckets and email templates (136 definitions) with a smoke test that runs the real flows against a deployed environment and its own offline tests.
189
+
190
+ ## Development
191
+
192
+ ```bash
193
+ pip install -e '.[test]'
194
+ pytest # the library, offline
195
+ ```
196
+
197
+ The conformance tests compare every definition's request body with the server's own request models, run flows through the server's flow validator and check the generated block catalogue against the running server's. They run where `pawabase_server` is importable:
198
+
199
+ ```bash
200
+ cd ../pawabase && PYTHONPATH=../pawabase-iac uv run python -m pytest ../pawabase-iac/tests/test_server_conformance.py
201
+ ```
202
+
203
+ Regenerate the flow blocks when the server gains some:
204
+
205
+ ```bash
206
+ cd ../pawabase && uv run python ../pawabase-iac/scripts/generate_blocks.py
207
+ ```
208
+
209
+ ```
210
+ pawabase_iac/
211
+ core/ base Definition, env(), the registry
212
+ expr/ P (policy conditions) and ctx (flow templates)
213
+ tables/ fields, Table, Schema, indexes, relations
214
+ policies/ Policy, Transformer, condition checks
215
+ access/ Role
216
+ flows/ Flow builder, graph checks, layout, catalogue.py and blocks/ (generated)
217
+ api/ Route
218
+ events/ Subscription, Schedule, Webhook, InboundHook
219
+ storage/ Bucket
220
+ mail/ MailTemplate
221
+ validate/ cross-definition reference checks
222
+ engine/ plan, apply, diff, what-it-serves
223
+ environments/ Remote (the Platform API) and Emulated (in memory)
224
+ project/ pawabase.toml, .env, importing your definitions
225
+ cli/ the pawabase command
226
+ ```
227
+
228
+ ## Limits
229
+
230
+ Environment-level settings are not managed from code yet: auth providers and settings, secrets, API keys, firewall rules, domains, alarms, dashboards, backup schedules, and mail and storage connection settings. Users are not managed either (give people roles in Accounts). The twelve kinds above are.
231
+
232
+ ## License
233
+
234
+ BSD 3-Clause. See [LICENSE](LICENSE).
@@ -0,0 +1,4 @@
1
+ .env
2
+ .env.local
3
+ .pawabase/
4
+ __pycache__/