fullscript.spellbook 2.2.0__tar.gz → 3.0.1__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 (51) hide show
  1. fullscript_spellbook-3.0.1/AGENTS.md +107 -0
  2. fullscript_spellbook-3.0.1/CLAUDE.md +1 -0
  3. {fullscript_spellbook-2.2.0 → fullscript_spellbook-3.0.1}/PKG-INFO +1 -1
  4. {fullscript_spellbook-2.2.0 → fullscript_spellbook-3.0.1}/documentation/db_magic.md +93 -0
  5. {fullscript_spellbook-2.2.0 → fullscript_spellbook-3.0.1}/fullscript.spellbook.egg-info/PKG-INFO +1 -1
  6. {fullscript_spellbook-2.2.0 → fullscript_spellbook-3.0.1}/fullscript.spellbook.egg-info/SOURCES.txt +6 -1
  7. {fullscript_spellbook-2.2.0 → fullscript_spellbook-3.0.1}/fullscript.spellbook.egg-info/scm_file_list.json +6 -1
  8. {fullscript_spellbook-2.2.0 → fullscript_spellbook-3.0.1}/fullscript.spellbook.egg-info/scm_version.json +2 -2
  9. {fullscript_spellbook-2.2.0 → fullscript_spellbook-3.0.1}/pyproject.toml +1 -1
  10. fullscript_spellbook-3.0.1/spellbook/__init__.py +19 -0
  11. fullscript_spellbook-3.0.1/spellbook/db_magic.py +635 -0
  12. fullscript_spellbook-3.0.1/spellbook/exceptions.py +41 -0
  13. fullscript_spellbook-3.0.1/spellbook/utils.py +238 -0
  14. fullscript_spellbook-3.0.1/tests/test_db_magic.py +594 -0
  15. fullscript_spellbook-3.0.1/tests/test_utils.py +257 -0
  16. fullscript_spellbook-2.2.0/spellbook/db_magic.py +0 -416
  17. fullscript_spellbook-2.2.0/spellbook/utils.py +0 -123
  18. fullscript_spellbook-2.2.0/tests/test_db_magic.py +0 -316
  19. {fullscript_spellbook-2.2.0 → fullscript_spellbook-3.0.1}/.gitignore +0 -0
  20. {fullscript_spellbook-2.2.0 → fullscript_spellbook-3.0.1}/.gitlab-ci.yml +0 -0
  21. {fullscript_spellbook-2.2.0 → fullscript_spellbook-3.0.1}/.python-version +0 -0
  22. {fullscript_spellbook-2.2.0 → fullscript_spellbook-3.0.1}/LICENSE +0 -0
  23. {fullscript_spellbook-2.2.0 → fullscript_spellbook-3.0.1}/MANIFEST.in +0 -0
  24. {fullscript_spellbook-2.2.0 → fullscript_spellbook-3.0.1}/README.md +0 -0
  25. {fullscript_spellbook-2.2.0 → fullscript_spellbook-3.0.1}/documentation/drive_magic.md +0 -0
  26. {fullscript_spellbook-2.2.0 → fullscript_spellbook-3.0.1}/documentation/sheet_magic.md +0 -0
  27. {fullscript_spellbook-2.2.0 → fullscript_spellbook-3.0.1}/documentation/snowflake_key_pair_setup.md +0 -0
  28. {fullscript_spellbook-2.2.0 → fullscript_spellbook-3.0.1}/documentation/stats_magic.md +0 -0
  29. {fullscript_spellbook-2.2.0 → fullscript_spellbook-3.0.1}/fullscript.spellbook.egg-info/dependency_links.txt +0 -0
  30. {fullscript_spellbook-2.2.0 → fullscript_spellbook-3.0.1}/fullscript.spellbook.egg-info/requires.txt +0 -0
  31. {fullscript_spellbook-2.2.0 → fullscript_spellbook-3.0.1}/fullscript.spellbook.egg-info/top_level.txt +0 -0
  32. {fullscript_spellbook-2.2.0 → fullscript_spellbook-3.0.1}/requirements.txt +0 -0
  33. {fullscript_spellbook-2.2.0 → fullscript_spellbook-3.0.1}/setup.cfg +0 -0
  34. {fullscript_spellbook-2.2.0 → fullscript_spellbook-3.0.1}/spellbook/drive_magic.py +0 -0
  35. /fullscript_spellbook-2.2.0/spellbook/__init__.py → /fullscript_spellbook-3.0.1/spellbook/py.typed +0 -0
  36. {fullscript_spellbook-2.2.0 → fullscript_spellbook-3.0.1}/spellbook/sheet_magic.py +0 -0
  37. {fullscript_spellbook-2.2.0 → fullscript_spellbook-3.0.1}/spellbook/stats_magic.py +0 -0
  38. {fullscript_spellbook-2.2.0 → fullscript_spellbook-3.0.1}/spellbook/wizards/__init__.py +0 -0
  39. {fullscript_spellbook-2.2.0 → fullscript_spellbook-3.0.1}/spellbook/wizards/wizard1.txt +0 -0
  40. {fullscript_spellbook-2.2.0 → fullscript_spellbook-3.0.1}/spellbook/wizards/wizard10.txt +0 -0
  41. {fullscript_spellbook-2.2.0 → fullscript_spellbook-3.0.1}/spellbook/wizards/wizard11.txt +0 -0
  42. {fullscript_spellbook-2.2.0 → fullscript_spellbook-3.0.1}/spellbook/wizards/wizard12.txt +0 -0
  43. {fullscript_spellbook-2.2.0 → fullscript_spellbook-3.0.1}/spellbook/wizards/wizard2.txt +0 -0
  44. {fullscript_spellbook-2.2.0 → fullscript_spellbook-3.0.1}/spellbook/wizards/wizard3.txt +0 -0
  45. {fullscript_spellbook-2.2.0 → fullscript_spellbook-3.0.1}/spellbook/wizards/wizard4.txt +0 -0
  46. {fullscript_spellbook-2.2.0 → fullscript_spellbook-3.0.1}/spellbook/wizards/wizard5.txt +0 -0
  47. {fullscript_spellbook-2.2.0 → fullscript_spellbook-3.0.1}/spellbook/wizards/wizard6.txt +0 -0
  48. {fullscript_spellbook-2.2.0 → fullscript_spellbook-3.0.1}/spellbook/wizards/wizard7.txt +0 -0
  49. {fullscript_spellbook-2.2.0 → fullscript_spellbook-3.0.1}/spellbook/wizards/wizard8.txt +0 -0
  50. {fullscript_spellbook-2.2.0 → fullscript_spellbook-3.0.1}/spellbook/wizards/wizard9.txt +0 -0
  51. {fullscript_spellbook-2.2.0 → fullscript_spellbook-3.0.1}/spellbook_config.yaml +0 -0
@@ -0,0 +1,107 @@
1
+ # Working on Spellbook
2
+
3
+ Notes for anyone - person or coding agent - changing this package. This file covers the
4
+ things that are not obvious from reading the code.
5
+
6
+ ## What this package is
7
+
8
+ A thin, friendly wrapper over Snowflake, MySQL, Google Sheets, Google Drive and some
9
+ statistics helpers, published publicly to PyPI as `fullscript.spellbook`.
10
+
11
+ **It is a public package.** Never commit Fullscript hostnames, database names, RDS instance
12
+ identifiers, usernames or internal URLs - not in code, examples, docs or tests. Use
13
+ placeholders (`${MYSQL_HOST}`, `your_rds_instance_name`) instead.
14
+
15
+ ## Running the tests
16
+
17
+ ```bash
18
+ pip install -e '.[test]'
19
+ pytest
20
+ ```
21
+
22
+ Tests mock the database and `subprocess`, so they need no network and no credentials.
23
+ `tests/test_db_magic.py` covers connections and auth, `tests/test_utils.py` covers config
24
+ loading. Testing against a real database needs AWS SSO, which no automated run can do.
25
+
26
+ ## Versioning
27
+
28
+ The version is **not** in a file. `setuptools_scm` derives it from git tags, so releasing
29
+ means tagging:
30
+
31
+ ```bash
32
+ git tag 3.0.0 && git push origin main --tags
33
+ ```
34
+
35
+ Pushing a tag triggers `.gitlab-ci.yml`, which publishes to both the internal registry and
36
+ public PyPI. Do not tag unless you intend to publish.
37
+
38
+ ## Conventions worth keeping
39
+
40
+ **Errors raise, they do not return empty.** Every failure path raises a subclass of
41
+ `SpellbookError` from `spellbook/exceptions.py`. Returning `pd.DataFrame()` or `None` on
42
+ failure is the thing this package deliberately moved away from: a caller cannot tell
43
+ "matched no rows" from "never ran", which produces confidently wrong analysis. An empty
44
+ DataFrame must only ever mean zero rows.
45
+
46
+ **Decorative output goes to stderr, never stdout.** The wizard ASCII art is charm, not
47
+ data. `utils.show_wizard()` honours `SPELLBOOK_QUIET=1`; set that when calling Spellbook
48
+ from a script or agent. Use `logging` for diagnostics rather than `print`.
49
+
50
+ **Function signatures are `(db_name, query)`, in that order.** `execute_mysql` once took
51
+ `(query, dbname)`, which silently broke every `run_sql` call against MySQL. Keep the order
52
+ and the `db_name` spelling consistent across every function.
53
+
54
+ **Bind parameters in catalogue queries.** `list_tables` and `describe_table` pass values
55
+ through SQLAlchemy parameters so a `%` or a quote in user input cannot reshape the query.
56
+ `preview_table` cannot bind a table name, so it validates it against a strict identifier
57
+ pattern instead.
58
+
59
+ ## Traps that have cost time before
60
+
61
+ **Validate the database being used, not the whole config.** The entire `databases` section
62
+ is re-parsed on every lookup, so any check that runs during parsing applies to databases the
63
+ caller never asked for. Release 3.0.0 raised on the first unset `${VAR}` anywhere in the
64
+ section, which meant an unrelated Snowflake entry with no key passphrase blocked every MySQL
65
+ query. `parse_env_variables` now substitutes permissively and records unresolved names under
66
+ `_unresolved_env`; `db_magic._validate_db_config` turns those into an error for the selected
67
+ database only, and only for settings in `REQUIRED_FIELDS`. Settings that are legitimately
68
+ empty - a Snowflake passphrase, `ssl_ca`, `port`, `schema` - must stay out of that list.
69
+
70
+ **Config file precedence.** `utils.find_config_file()` prefers `spellbook_config.yaml` in
71
+ the *current working directory* over the one in your home directory. Running anything from
72
+ this repository picks up the placeholder example config, not your real one. If a connection
73
+ fails with odd credentials, check which file was loaded.
74
+
75
+ **Two MySQL drivers.** Password auth uses `mysql+mysqlconnector`; AWS RDS IAM auth uses
76
+ `mysql+pymysql`, because only pymysql completes the IAM token handshake. They are not
77
+ interchangeable:
78
+
79
+ - pymysql substitutes parameters whenever `args is not None`, and pandas passes an empty
80
+ tuple, so a literal `%` in `like '%value%'` is read as a format character and raises.
81
+ mysql-connector guards on `if params:` instead. `db_magic._iam_cursor_class()` normalises
82
+ empty parameters to `None` so both drivers behave the same. Do not remove it without a
83
+ test that runs a `like '%...%'` query.
84
+ - An empty `ssl` dict makes TLS merely *preferred* in pymysql. The dict passed must be
85
+ truthy for TLS to be required.
86
+
87
+ **RDS IAM specifics.** Tokens expire in about 15 minutes, so they are fetched per
88
+ connection and never cached. `rx rds password` takes the *RDS instance* name, which usually
89
+ differs from the config alias - hence the optional `rds_db_name` field. The token is URL
90
+ shaped and must be `quote_plus`-escaped into the connection string.
91
+
92
+ **`iam_auth` is internal only.** It shells out to `rx`, which is Fullscript internal
93
+ tooling, so the feature cannot work for public users of this package. Keep password auth
94
+ working and keep the docs honest about the limitation.
95
+
96
+ ## Repository layout
97
+
98
+ | Path | Purpose |
99
+ | --- | --- |
100
+ | `spellbook/db_magic.py` | Snowflake and MySQL connections, queries, schema helpers |
101
+ | `spellbook/exceptions.py` | The `SpellbookError` hierarchy |
102
+ | `spellbook/utils.py` | Config loading, env substitution, wizards, SQL file loading |
103
+ | `spellbook/sheet_magic.py` | Google Sheets |
104
+ | `spellbook/drive_magic.py` | Google Drive |
105
+ | `spellbook/stats_magic.py` | Statistical tests |
106
+ | `spellbook_config.yaml` | **Example** config, published - placeholders only |
107
+ | `documentation/` | User-facing docs per module |
@@ -0,0 +1 @@
1
+ See [AGENTS.md](AGENTS.md) for how to work on this package.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fullscript.spellbook
3
- Version: 2.2.0
3
+ Version: 3.0.1
4
4
  Summary: A simple package to make your life easier.
5
5
  Author-email: Meinhard Mare <meinhard.mare@fullscript.com>, Thomas Edwards <thomas.edwards@fullscript.com>, Gui Chagas <gui.chagas@fullscript.com>, David King <david.king@fullscript.com>, Karsten Melchers <karsten.melchers@fullscript.com>
6
6
  License: MIT
@@ -10,6 +10,48 @@ A magical wrapper to perform work with databases.
10
10
  from spellbook import db_magic
11
11
  ```
12
12
 
13
+ ### Error Handling
14
+
15
+ Every failure raises an exception. An empty DataFrame always means "the query matched no
16
+ rows" and never "something went wrong", so results can be trusted without extra checks.
17
+
18
+ ```python
19
+ from spellbook import db_magic
20
+ from spellbook.exceptions import SpellbookError, QueryError, DatabaseNotFoundError
21
+
22
+ try:
23
+ df = db_magic.get_data('reporting_db', 'select * from users limit 10')
24
+ except DatabaseNotFoundError:
25
+ ... # the name is not in spellbook_config.yaml
26
+ except QueryError:
27
+ ... # the database rejected the query
28
+ except SpellbookError:
29
+ ... # anything Spellbook raises
30
+ ```
31
+
32
+ | Exception | Raised when |
33
+ | --- | --- |
34
+ | `SpellbookError` | Base class for all of the below |
35
+ | `ConfigError` | The config file is missing, malformed, or references an unset `${VAR}` |
36
+ | `DatabaseNotFoundError` | No database in the config matches the requested name |
37
+ | `AuthenticationError` | Credentials could not be obtained or were rejected |
38
+ | `ConnectionFailedError` | The database could not be reached |
39
+ | `QueryError` | The query was rejected or failed while running |
40
+
41
+ > **Upgrading from 2.x:** these functions used to print a message and return an empty
42
+ > DataFrame or `None` on failure. Code that inspected `df.empty` to detect *errors* now
43
+ > needs a `try`/`except`. Code that used `df.empty` to detect *zero rows* is unaffected.
44
+
45
+ ### Quiet Mode
46
+
47
+ Spellbook prints a wizard for company on each query. It goes to stderr, never stdout, so it
48
+ cannot contaminate results. Set `SPELLBOOK_QUIET=1` to turn it off - recommended for
49
+ scripts, scheduled jobs and anything driven by an AI agent.
50
+
51
+ ```bash
52
+ export SPELLBOOK_QUIET=1
53
+ ```
54
+
13
55
  ### MySQL Authentication
14
56
 
15
57
  MySQL connections support two authentication methods, chosen per database in `spellbook_config.yaml`.
@@ -159,3 +201,54 @@ db_magic.run_sql("mysql_db", update_query)
159
201
 
160
202
  print("SQL executed successfully!")
161
203
  ```
204
+
205
+ ### Exploring a database
206
+
207
+ These helpers answer "what is in here?" without hand-writing `information_schema` queries.
208
+ They are useful interactively, and they are what an AI agent needs before it can write a
209
+ sensible query.
210
+
211
+ ##### list_databases()
212
+
213
+ Lists the databases in your configuration file, with no credentials included.
214
+
215
+ ```python
216
+ db_magic.list_databases()
217
+ # name type database iam_auth
218
+ # 0 reporting_db mysql mysql_database True
219
+ ```
220
+
221
+ ##### list_tables(db_name, pattern=None)
222
+
223
+ Lists the tables in a database, optionally filtered by a case-insensitive substring.
224
+
225
+ ```python
226
+ db_magic.list_tables("mysql_db")
227
+ db_magic.list_tables("mysql_db", pattern="order")
228
+ ```
229
+
230
+ The pattern is passed as a bound parameter, so `%` and quotes in it are treated as literal
231
+ text rather than changing the query.
232
+
233
+ ##### describe_table(db_name, table)
234
+
235
+ Returns one row per column, with its type, nullability and default. Raises `QueryError` if
236
+ the table does not exist.
237
+
238
+ ```python
239
+ db_magic.describe_table("mysql_db", "users")
240
+ # column_name data_type is_nullable column_default
241
+ # 0 id bigint NO None
242
+ # 1 name varchar YES None
243
+ ```
244
+
245
+ ##### preview_table(db_name, table, limit=10)
246
+
247
+ Reads the first few rows of a table.
248
+
249
+ ```python
250
+ db_magic.preview_table("mysql_db", "users", limit=5)
251
+ ```
252
+
253
+ `table` must be a plain identifier - letters, digits, underscores, dots or dollar signs -
254
+ because a table name cannot be passed as a bound parameter.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fullscript.spellbook
3
- Version: 2.2.0
3
+ Version: 3.0.1
4
4
  Summary: A simple package to make your life easier.
5
5
  Author-email: Meinhard Mare <meinhard.mare@fullscript.com>, Thomas Edwards <thomas.edwards@fullscript.com>, Gui Chagas <gui.chagas@fullscript.com>, David King <david.king@fullscript.com>, Karsten Melchers <karsten.melchers@fullscript.com>
6
6
  License: MIT
@@ -1,6 +1,8 @@
1
1
  .gitignore
2
2
  .gitlab-ci.yml
3
3
  .python-version
4
+ AGENTS.md
5
+ CLAUDE.md
4
6
  LICENSE
5
7
  MANIFEST.in
6
8
  README.md
@@ -22,6 +24,8 @@ fullscript.spellbook.egg-info/top_level.txt
22
24
  spellbook/__init__.py
23
25
  spellbook/db_magic.py
24
26
  spellbook/drive_magic.py
27
+ spellbook/exceptions.py
28
+ spellbook/py.typed
25
29
  spellbook/sheet_magic.py
26
30
  spellbook/stats_magic.py
27
31
  spellbook/utils.py
@@ -38,4 +42,5 @@ spellbook/wizards/wizard6.txt
38
42
  spellbook/wizards/wizard7.txt
39
43
  spellbook/wizards/wizard8.txt
40
44
  spellbook/wizards/wizard9.txt
41
- tests/test_db_magic.py
45
+ tests/test_db_magic.py
46
+ tests/test_utils.py
@@ -3,6 +3,8 @@
3
3
  ".gitignore",
4
4
  ".gitlab-ci.yml",
5
5
  ".python-version",
6
+ "AGENTS.md",
7
+ "CLAUDE.md",
6
8
  "LICENSE",
7
9
  "MANIFEST.in",
8
10
  "README.md",
@@ -16,6 +18,8 @@
16
18
  "spellbook/__init__.py",
17
19
  "spellbook/db_magic.py",
18
20
  "spellbook/drive_magic.py",
21
+ "spellbook/exceptions.py",
22
+ "spellbook/py.typed",
19
23
  "spellbook/sheet_magic.py",
20
24
  "spellbook/stats_magic.py",
21
25
  "spellbook/utils.py",
@@ -33,6 +37,7 @@
33
37
  "spellbook/wizards/wizard8.txt",
34
38
  "spellbook/wizards/wizard9.txt",
35
39
  "spellbook_config.yaml",
36
- "tests/test_db_magic.py"
40
+ "tests/test_db_magic.py",
41
+ "tests/test_utils.py"
37
42
  ]
38
43
  }
@@ -1,7 +1,7 @@
1
1
  {
2
- "tag": "2.2.0",
2
+ "tag": "3.0.1",
3
3
  "distance": 0,
4
- "node": "gb79ed51cfa3c9db792124b375e8edb615250590b",
4
+ "node": "g4c7e282904d3f4b9436cecd6265dbc228d36c8de",
5
5
  "dirty": false,
6
6
  "branch": "HEAD",
7
7
  "node_date": "2026-10-22"
@@ -146,6 +146,6 @@ where = ["."]
146
146
  include = ["spellbook"]
147
147
 
148
148
  [tool.setuptools.package-data]
149
- "spellbook" = ["wizards/*.txt"]
149
+ "spellbook" = ["wizards/*.txt", "py.typed"]
150
150
 
151
151
  [tool.setuptools_scm]
@@ -0,0 +1,19 @@
1
+ """Spellbook: connect to databases, spreadsheets and Drive, and run analyses."""
2
+
3
+ from spellbook.exceptions import (
4
+ AuthenticationError,
5
+ ConfigError,
6
+ ConnectionFailedError,
7
+ DatabaseNotFoundError,
8
+ QueryError,
9
+ SpellbookError,
10
+ )
11
+
12
+ __all__ = [
13
+ 'SpellbookError',
14
+ 'ConfigError',
15
+ 'DatabaseNotFoundError',
16
+ 'AuthenticationError',
17
+ 'ConnectionFailedError',
18
+ 'QueryError',
19
+ ]