squirrels 0.4.1__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.

Potentially problematic release.


This version of squirrels might be problematic. Click here for more details.

Files changed (125) hide show
  1. dateutils/__init__.py +6 -0
  2. dateutils/_enums.py +25 -0
  3. squirrels/dateutils.py → dateutils/_implementation.py +58 -111
  4. dateutils/types.py +6 -0
  5. squirrels/__init__.py +13 -11
  6. squirrels/_api_routes/__init__.py +5 -0
  7. squirrels/_api_routes/auth.py +271 -0
  8. squirrels/_api_routes/base.py +165 -0
  9. squirrels/_api_routes/dashboards.py +150 -0
  10. squirrels/_api_routes/data_management.py +145 -0
  11. squirrels/_api_routes/datasets.py +257 -0
  12. squirrels/_api_routes/oauth2.py +298 -0
  13. squirrels/_api_routes/project.py +252 -0
  14. squirrels/_api_server.py +256 -450
  15. squirrels/_arguments/__init__.py +0 -0
  16. squirrels/_arguments/init_time_args.py +108 -0
  17. squirrels/_arguments/run_time_args.py +147 -0
  18. squirrels/_auth.py +960 -0
  19. squirrels/_command_line.py +126 -45
  20. squirrels/_compile_prompts.py +147 -0
  21. squirrels/_connection_set.py +48 -26
  22. squirrels/_constants.py +68 -38
  23. squirrels/_dashboards.py +160 -0
  24. squirrels/_data_sources.py +570 -0
  25. squirrels/_dataset_types.py +84 -0
  26. squirrels/_exceptions.py +29 -0
  27. squirrels/_initializer.py +177 -80
  28. squirrels/_logging.py +115 -0
  29. squirrels/_manifest.py +208 -79
  30. squirrels/_model_builder.py +69 -0
  31. squirrels/_model_configs.py +74 -0
  32. squirrels/_model_queries.py +52 -0
  33. squirrels/_models.py +926 -367
  34. squirrels/_package_data/base_project/.env +42 -0
  35. squirrels/_package_data/base_project/.env.example +42 -0
  36. squirrels/_package_data/base_project/assets/expenses.db +0 -0
  37. squirrels/_package_data/base_project/connections.yml +16 -0
  38. squirrels/_package_data/base_project/dashboards/dashboard_example.py +34 -0
  39. squirrels/_package_data/base_project/dashboards/dashboard_example.yml +22 -0
  40. squirrels/{package_data → _package_data}/base_project/docker/.dockerignore +5 -2
  41. squirrels/{package_data → _package_data}/base_project/docker/Dockerfile +3 -3
  42. squirrels/{package_data → _package_data}/base_project/docker/compose.yml +1 -1
  43. squirrels/_package_data/base_project/duckdb_init.sql +10 -0
  44. squirrels/{package_data/base_project/.gitignore → _package_data/base_project/gitignore} +3 -2
  45. squirrels/_package_data/base_project/macros/macros_example.sql +17 -0
  46. squirrels/_package_data/base_project/models/builds/build_example.py +26 -0
  47. squirrels/_package_data/base_project/models/builds/build_example.sql +16 -0
  48. squirrels/_package_data/base_project/models/builds/build_example.yml +57 -0
  49. squirrels/_package_data/base_project/models/dbviews/dbview_example.sql +12 -0
  50. squirrels/_package_data/base_project/models/dbviews/dbview_example.yml +26 -0
  51. squirrels/_package_data/base_project/models/federates/federate_example.py +37 -0
  52. squirrels/_package_data/base_project/models/federates/federate_example.sql +19 -0
  53. squirrels/_package_data/base_project/models/federates/federate_example.yml +65 -0
  54. squirrels/_package_data/base_project/models/sources.yml +38 -0
  55. squirrels/{package_data → _package_data}/base_project/parameters.yml +56 -40
  56. squirrels/_package_data/base_project/pyconfigs/connections.py +14 -0
  57. squirrels/{package_data → _package_data}/base_project/pyconfigs/context.py +21 -40
  58. squirrels/_package_data/base_project/pyconfigs/parameters.py +141 -0
  59. squirrels/_package_data/base_project/pyconfigs/user.py +44 -0
  60. squirrels/_package_data/base_project/seeds/seed_categories.yml +15 -0
  61. squirrels/_package_data/base_project/seeds/seed_subcategories.csv +15 -0
  62. squirrels/_package_data/base_project/seeds/seed_subcategories.yml +21 -0
  63. squirrels/_package_data/base_project/squirrels.yml.j2 +61 -0
  64. squirrels/_package_data/templates/dataset_results.html +112 -0
  65. squirrels/_package_data/templates/oauth_login.html +271 -0
  66. squirrels/_package_data/templates/squirrels_studio.html +20 -0
  67. squirrels/_package_loader.py +8 -4
  68. squirrels/_parameter_configs.py +104 -103
  69. squirrels/_parameter_options.py +348 -0
  70. squirrels/_parameter_sets.py +57 -47
  71. squirrels/_parameters.py +1664 -0
  72. squirrels/_project.py +721 -0
  73. squirrels/_py_module.py +7 -5
  74. squirrels/_schemas/__init__.py +0 -0
  75. squirrels/_schemas/auth_models.py +167 -0
  76. squirrels/_schemas/query_param_models.py +75 -0
  77. squirrels/{_api_response_models.py → _schemas/response_models.py} +126 -47
  78. squirrels/_seeds.py +35 -16
  79. squirrels/_sources.py +110 -0
  80. squirrels/_utils.py +248 -73
  81. squirrels/_version.py +1 -1
  82. squirrels/arguments.py +7 -0
  83. squirrels/auth.py +4 -0
  84. squirrels/connections.py +3 -0
  85. squirrels/dashboards.py +2 -81
  86. squirrels/data_sources.py +14 -631
  87. squirrels/parameter_options.py +13 -348
  88. squirrels/parameters.py +14 -1266
  89. squirrels/types.py +16 -0
  90. squirrels-0.5.0.dist-info/METADATA +113 -0
  91. squirrels-0.5.0.dist-info/RECORD +97 -0
  92. {squirrels-0.4.1.dist-info → squirrels-0.5.0.dist-info}/WHEEL +1 -1
  93. squirrels-0.5.0.dist-info/entry_points.txt +3 -0
  94. {squirrels-0.4.1.dist-info → squirrels-0.5.0.dist-info/licenses}/LICENSE +1 -1
  95. squirrels/_authenticator.py +0 -85
  96. squirrels/_dashboards_io.py +0 -61
  97. squirrels/_environcfg.py +0 -84
  98. squirrels/arguments/init_time_args.py +0 -40
  99. squirrels/arguments/run_time_args.py +0 -208
  100. squirrels/package_data/assets/favicon.ico +0 -0
  101. squirrels/package_data/assets/index.css +0 -1
  102. squirrels/package_data/assets/index.js +0 -58
  103. squirrels/package_data/base_project/assets/expenses.db +0 -0
  104. squirrels/package_data/base_project/connections.yml +0 -7
  105. squirrels/package_data/base_project/dashboards/dashboard_example.py +0 -32
  106. squirrels/package_data/base_project/dashboards.yml +0 -10
  107. squirrels/package_data/base_project/env.yml +0 -29
  108. squirrels/package_data/base_project/models/dbviews/dbview_example.py +0 -47
  109. squirrels/package_data/base_project/models/dbviews/dbview_example.sql +0 -22
  110. squirrels/package_data/base_project/models/federates/federate_example.py +0 -21
  111. squirrels/package_data/base_project/models/federates/federate_example.sql +0 -3
  112. squirrels/package_data/base_project/pyconfigs/auth.py +0 -45
  113. squirrels/package_data/base_project/pyconfigs/connections.py +0 -19
  114. squirrels/package_data/base_project/pyconfigs/parameters.py +0 -95
  115. squirrels/package_data/base_project/seeds/seed_subcategories.csv +0 -15
  116. squirrels/package_data/base_project/squirrels.yml.j2 +0 -94
  117. squirrels/package_data/templates/index.html +0 -18
  118. squirrels/project.py +0 -378
  119. squirrels/user_base.py +0 -55
  120. squirrels-0.4.1.dist-info/METADATA +0 -117
  121. squirrels-0.4.1.dist-info/RECORD +0 -60
  122. squirrels-0.4.1.dist-info/entry_points.txt +0 -4
  123. /squirrels/{package_data → _package_data}/base_project/assets/weather.db +0 -0
  124. /squirrels/{package_data → _package_data}/base_project/seeds/seed_categories.csv +0 -0
  125. /squirrels/{package_data → _package_data}/base_project/tmp/.gitignore +0 -0
squirrels/types.py ADDED
@@ -0,0 +1,16 @@
1
+ from ._data_sources import DataSource
2
+
3
+ from ._parameter_options import ParameterOption
4
+
5
+ from ._parameters import Parameter, TextValue
6
+
7
+ from ._dataset_types import DatasetMetadata, DatasetResult
8
+
9
+ from ._dashboards import Dashboard
10
+
11
+ from ._parameter_configs import ParameterConfigBase
12
+
13
+ __all__ = [
14
+ "DataSource", "ParameterOption", "Parameter", "TextValue",
15
+ "DatasetMetadata", "DatasetResult", "Dashboard", "ParameterConfigBase"
16
+ ]
@@ -0,0 +1,113 @@
1
+ Metadata-Version: 2.4
2
+ Name: squirrels
3
+ Version: 0.5.0
4
+ Summary: Squirrels - API Framework for Data Analytics
5
+ Project-URL: Homepage, https://squirrels-analytics.github.io
6
+ Project-URL: Repository, https://github.com/squirrels-analytics/squirrels
7
+ Project-URL: Documentation, https://squirrels-analytics.github.io
8
+ Author-email: Tim Huang <tim.yuting@hotmail.com>
9
+ License-Expression: Apache-2.0
10
+ License-File: LICENSE
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
13
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
14
+ Classifier: Typing :: Typed
15
+ Requires-Python: ~=3.10
16
+ Requires-Dist: authlib<2,>=1.5.2
17
+ Requires-Dist: bcrypt<5,>=4.0.1
18
+ Requires-Dist: cachetools>=5.3.2
19
+ Requires-Dist: duckdb<2,>=1.4.0
20
+ Requires-Dist: fastapi<1,>=0.112.1
21
+ Requires-Dist: gitpython<4,>=3.1.41
22
+ Requires-Dist: inquirer<4,>=3.2.1
23
+ Requires-Dist: itsdangerous<3,>=2.2.0
24
+ Requires-Dist: jinja2<4,>=3.1.3
25
+ Requires-Dist: libpass<2,>=1.9.0
26
+ Requires-Dist: matplotlib<4,>=3.8.3
27
+ Requires-Dist: mcp>=1.13.1
28
+ Requires-Dist: pandas<3,>=2.1.4
29
+ Requires-Dist: polars<2,>=1.14.0
30
+ Requires-Dist: pyarrow>=19.0.1
31
+ Requires-Dist: pydantic<3,>=2.8.2
32
+ Requires-Dist: pyjwt<3,>=2.8.0
33
+ Requires-Dist: python-dotenv<2,>=1.0.1
34
+ Requires-Dist: python-multipart<1,>=0.0.9
35
+ Requires-Dist: pyyaml<7,>=6.0.1
36
+ Requires-Dist: sqlalchemy<3,>=2.0.25
37
+ Requires-Dist: sqlglot>=26.12.1
38
+ Requires-Dist: uvicorn<1,>=0.30.6
39
+ Description-Content-Type: text/markdown
40
+
41
+ # Squirrels
42
+
43
+ Squirrels is an API framework that lets you create REST APIs for dynamic data analytics!
44
+
45
+ **Documentation**: <a href="https://squirrels-analytics.github.io/" target="_blank">https://squirrels-analytics.github.io/</a>
46
+
47
+ **Source Code**: <a href="https://github.com/squirrels-analytics/squirrels" target="_blank">https://github.com/squirrels-analytics/squirrels</a>
48
+
49
+ ## Table of Contents
50
+
51
+ - [Main Features](#main-features)
52
+ - [License](#license)
53
+ - [Contributing to Squirrels](#contributing-to-squirrels)
54
+ - [Setup](#setup)
55
+ - [Testing](#testing)
56
+ - [Project Structure](#project-structure)
57
+
58
+ ## Main Features
59
+
60
+ Here are a few of the things that Squirrels can do:
61
+
62
+ - Connect to any database by specifying its SQLAlchemy url (in `squirrels.yml`) or by using its native connector library in python (in `connections.py`).
63
+ - Configure API routes for datasets (in `squirrels.yml`) without writing code.
64
+ - Configure parameter widgets (types include single-select, multi-select, date, number, etc.) for your datasets (in `parameters.py`).
65
+ - Use SQL templates (templated with Jinja, like dbt) or python functions (that return a Python dataframe in polars or pandas) to define dynamic query logic based on parameter selections.
66
+ - Query multiple databases and join the results together in a final view in one API endpoint/dataset!
67
+ - Test your API endpoints with Squirrels Studio or by a command line that generates rendered sql queries and results as files (for a given set of parameter selections).
68
+ - Define User model (in `user.py`) and authorize privacy scope per dataset (in `squirrels.yml`). The user's attributes can even be used in your query logic!
69
+ - Serve dataset metadata and results to AI agents via MCP (Model Context Protocol)
70
+
71
+ ## License
72
+
73
+ Squirrels is released under the Apache 2.0 license.
74
+
75
+ See the file LICENSE for more details.
76
+
77
+ ## Contributing to Squirrels
78
+
79
+ The sections below describe how to set up your local environment for Squirrels development and run unit tests. A high level overview of the project structure is also provided.
80
+
81
+ ### Setup
82
+
83
+ This project requires python version 3.10 or above to be installed. It also uses the python package manager `uv`. Information on setting up poetry can be found at: https://docs.astral.sh/uv/getting-started/installation/.
84
+
85
+ Then, to install all dependencies in a virtual environment, run:
86
+
87
+ ```bash
88
+ uv sync
89
+ ```
90
+
91
+ And activate the virtual environment with:
92
+
93
+ ```bash
94
+ source .venv/bin/activate
95
+ ```
96
+
97
+ To confirm that the setup worked, run the following to show the help page for all Squirrels CLI commands:
98
+
99
+ ```bash
100
+ sqrl -h
101
+ ```
102
+
103
+ ### Testing
104
+
105
+ Run `uv run pytest`. Or if you have the virtual environment activated, simply run `pytest`.
106
+
107
+ ### Project Structure
108
+
109
+ From the root of the git repo, the source code can be found in the `squirrels` folder and unit tests can be found in the `tests` folder.
110
+
111
+ To understand what a specific Squirrels command is doing, start from the `_command_line.py` file as your entry point.
112
+
113
+ The library version is maintained in both the `pyproject.toml` and the `squirrels/_version.py` files.
@@ -0,0 +1,97 @@
1
+ dateutils/__init__.py,sha256=dq4VSlJ5ztaDPdvYBRAvXSyanT_CZif3I4O0YVmWfa8,277
2
+ dateutils/_enums.py,sha256=WBrnLqta_iMMhGMEn24cCO1Vlr7bST0E8oEfAL3z0P8,373
3
+ dateutils/_implementation.py,sha256=PVJAdNolDdTpCZXwvokKoMIZzTSNpCUvZlVLWpMeSho,15173
4
+ dateutils/types.py,sha256=xcRwBoftQi-uHM1tlVTW5xgN84qdrqCo5tQT7A34S8Y,124
5
+ squirrels/__init__.py,sha256=nvzVVd5yHYbpQAfyKdQGDMxZpW7wVq3uUlpg4Urc_ws,320
6
+ squirrels/_api_server.py,sha256=kGqVF-QGG7VoaR9VxOO-0wuj8OvbbNV7ZsL34VAQ0O0,17454
7
+ squirrels/_auth.py,sha256=2yEYrK9-w-mtRPUkv486nic0WTVfb_UIbWl7DiGtSyg,43572
8
+ squirrels/_command_line.py,sha256=wTJr_WahULV8iZwUyhJIiNTGFd0YByprpo9DBDyczpU,12797
9
+ squirrels/_compile_prompts.py,sha256=XIbRciSXak6NuDD4KedeDoUV5YSHvctQGk6HMxKchBw,5562
10
+ squirrels/_connection_set.py,sha256=qSNcFdEKRcgZyr9OEqAuoIZBk-DY_C_aL_F7OE9GDK4,4457
11
+ squirrels/_constants.py,sha256=SuAaceUN95BLdOt500A8fUx88dvwm-v-WBj3v87z4qM,4035
12
+ squirrels/_dashboards.py,sha256=FF6KG-GXohR5Hp-BMYRwEZ2hUQ_jbkFpcK5epwoD64w,4891
13
+ squirrels/_data_sources.py,sha256=jiTzYsECny4KBnalzuHYy6wQo4YXubJesDLH_a1M3dc,26433
14
+ squirrels/_dataset_types.py,sha256=J9nhLVZ1eOk_qfqjEXRaXQTB9sqyZcE_iUad0NNstgU,2742
15
+ squirrels/_exceptions.py,sha256=-hfDZoV-JhvuNRAERTFg-5NFjr0uNk4i9ogNgJfAyyc,1171
16
+ squirrels/_initializer.py,sha256=9BDMGVbZLdRnV9erzqw1FvpKBlWRk8-uiSsl9UioxJ0,14249
17
+ squirrels/_logging.py,sha256=t0stGreMPXGRAB5cL2so2cmlMXpMSqRc0uepi2mooqE,4004
18
+ squirrels/_manifest.py,sha256=S4TicbPm5-4u9gEocbeO1dKb8CgTV8rsR5RERFjpkU0,12779
19
+ squirrels/_model_builder.py,sha256=gOAcN_C0dfAvBe9YTM3QpdiA_2VeatRSHsALp2OVdJA,2844
20
+ squirrels/_model_configs.py,sha256=eJne5L-QLv42s7uodUOOASMRn1NbzGbiBmwMllkO5C4,3303
21
+ squirrels/_model_queries.py,sha256=2fl07feHtzddBpiXUWGfNBYeoN_EZ7K5mAz2Jc-vCvs,1087
22
+ squirrels/_models.py,sha256=KiGjr1rgU4p4UX4c8fZR6CQ86w7qvSyEEbNnu3Hkby0,52213
23
+ squirrels/_package_loader.py,sha256=xcIur5Z38OWd-OVjsueFstVV567zlkK9UBnLS4NawJY,1158
24
+ squirrels/_parameter_configs.py,sha256=K7p_pIobT-gPZ99ewzLKV1aUzhnmfbGO7WjuzgjhDM8,24867
25
+ squirrels/_parameter_options.py,sha256=HatFFPyHrZo2ct3BS-FCHPzNqcIst4dVh14SgkhhFyw,16874
26
+ squirrels/_parameter_sets.py,sha256=hwzgC4sm6KMIASGlaLlhg-UheH5p0qIKU483pZRLRBU,10275
27
+ squirrels/_parameters.py,sha256=Ta929_a-b8r1miDKXT_SZtHT_d8jJ2W5ABvkXL7k074,76372
28
+ squirrels/_project.py,sha256=7vhYrDFL_Bgpj3JLAA25VfNa9UEQdqGGFYbCJKWQgeo,37010
29
+ squirrels/_py_module.py,sha256=NW86sv1oYWh8oMqgGh2oIUwuFcLXoq3cwEuKRxPfEwY,2683
30
+ squirrels/_seeds.py,sha256=Q6ihhou5PBfdH2mKhWEkDOfpdi1CmftUtv8Yz3S18dg,2216
31
+ squirrels/_sources.py,sha256=3tgI5FtMDzFtWBiHa2vF3x4Qaj5Pnu6hK_xSGBAcORI,5102
32
+ squirrels/_utils.py,sha256=HKDv-Z4_lEUaKyOOhsDtdf-lAOA6aR4Iw0bHg_aKT4k,14710
33
+ squirrels/_version.py,sha256=M8aFbJ4vlAi3Sk9b7leRuEfkNBjkkX5S_F9lA4h8GK4,105
34
+ squirrels/arguments.py,sha256=6rWDwv8jbimBUYv4peE2kEuFzOQ4jn9Tr_QXUmEj0_o,324
35
+ squirrels/auth.py,sha256=oX5-0QT4aKiUSyImKkLtPLYoki1SVNgaU3KBQzIizP4,193
36
+ squirrels/connections.py,sha256=FVK0se1LUAzsapPZYlWc1q6jTJ6hiU2RuYinBEvZ5a0,122
37
+ squirrels/dashboards.py,sha256=egYWL7jV9hvyxZ0vFziQb4q3sKDLfE_TNJN5niQbzX4,98
38
+ squirrels/data_sources.py,sha256=P3VIGWqspXLlxKUTDXc5u5eJ2yQoo4rvy9RGEb_OIbQ,347
39
+ squirrels/parameter_options.py,sha256=5ucgTWvdwefurMrk7xiJhmRn6fbBXXOhQdH7Mbnm-1I,382
40
+ squirrels/parameters.py,sha256=TJcsjtj0Kg0F69I5Zk7r0uDJC7p8duk3gtjXjcQPE40,366
41
+ squirrels/types.py,sha256=O4SMn9mqeV3wSEr_RIT1CKjumbqcbmZ6wk0tnXcxYHQ,437
42
+ squirrels/_api_routes/__init__.py,sha256=-oGMDfM5Qo7NDiwpjHt8BKVZNNZ4798-NZCJzYCUdbc,106
43
+ squirrels/_api_routes/auth.py,sha256=AvQbvyjrgEgN0kWPuBs8rgfJpsDL9ETFQbeFJ9S6qVw,15206
44
+ squirrels/_api_routes/base.py,sha256=TgzRB_yJ62xdIa90RRJfUbxtV_bWc6wsbNGn7A1rrMM,7772
45
+ squirrels/_api_routes/dashboards.py,sha256=dY-FFO2zJA2g9QLw7I98hXzurJHrYe2FTdPGxKPRMD8,8904
46
+ squirrels/_api_routes/data_management.py,sha256=oT_4etdDH55jI3fGbFkBgED9R2ji003S2HzSSc9JGpU,8607
47
+ squirrels/_api_routes/datasets.py,sha256=v-qWCvLiJIKeuzDLXEoerK_By_02iQdvhDvNkKpMYW8,15256
48
+ squirrels/_api_routes/oauth2.py,sha256=JXx5svIz0gVt6SHFOG-Ql9QcoJIizQ5IVFPPnZ5V1Bg,14827
49
+ squirrels/_api_routes/project.py,sha256=LxCkKmlrt7fojpRDRVl7ac-FstWyIIRM-uQWGXIE06w,14146
50
+ squirrels/_arguments/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
51
+ squirrels/_arguments/init_time_args.py,sha256=Y5gVbrU_yd1r5JtAChcL1xTpfz0i8gwNtZMe5QBT2mc,3511
52
+ squirrels/_arguments/run_time_args.py,sha256=bnibUxUHnLvKR1tlri0WCuqRnYKXi0mJ8R_rhIDEil0,5100
53
+ squirrels/_package_data/base_project/.env,sha256=KfUwxOG5KFicU1E-FkP8EyQRvAx5tAqXbZwtZxBPf08,1619
54
+ squirrels/_package_data/base_project/.env.example,sha256=XPqYRUVCim1MnPU8iJe5y_sRpp9HObDrJk7NFd-SSao,1656
55
+ squirrels/_package_data/base_project/connections.yml,sha256=eBIbTQqyp5FlxDs0HyQ_NuE2Nx87EW53Qp1Y_c1ygv0,1093
56
+ squirrels/_package_data/base_project/duckdb_init.sql,sha256=PYUiQZajja-TG7EU1__PoiyDajgngEil4Lxenv5FWT0,284
57
+ squirrels/_package_data/base_project/gitignore,sha256=B9OEkQ_j9fZGA2IAyVUvXeylxpya-AUwzLzqzMN4Bfc,155
58
+ squirrels/_package_data/base_project/parameters.yml,sha256=XJpbJ-kpgEnUBM45pN57Y9MOatPx5j-r2vM8_T4sw3U,7103
59
+ squirrels/_package_data/base_project/squirrels.yml.j2,sha256=8wOaGd1jiEfbUJFAQcdbLQioonsU_UDUaRYyJ3dKm3U,1910
60
+ squirrels/_package_data/base_project/assets/expenses.db,sha256=aO0QdApW9ad8LRc73MW1o3eimryzmOAH5vz9Vc3dWK0,77824
61
+ squirrels/_package_data/base_project/assets/weather.db,sha256=dsHPO36gQdZ4ULAA726Hg3jp8a1dCdig1DhrGg8wTeg,86016
62
+ squirrels/_package_data/base_project/dashboards/dashboard_example.py,sha256=jd6Sjs69QK4zBJlRnKaloati1cU5Oby1Pzg9bUxY0ZY,1637
63
+ squirrels/_package_data/base_project/dashboards/dashboard_example.yml,sha256=fIm56M3J1eWp9_zDcc5R04pSLbHgdjm2X3XZgpB0SaQ,417
64
+ squirrels/_package_data/base_project/docker/.dockerignore,sha256=IN0ZmxwLdmYlw6I2ziTdzXkTbZWCUyV4kfUI9_lDz-A,201
65
+ squirrels/_package_data/base_project/docker/Dockerfile,sha256=DBOfPajd7ikNr1Qg08TcQmQmWDYamlxoDv7u6FSWElE,470
66
+ squirrels/_package_data/base_project/docker/compose.yml,sha256=xMAjfJeNVv49ypMGxR8bG27P5JSbPTNMR7UeGTHGyeA,105
67
+ squirrels/_package_data/base_project/macros/macros_example.sql,sha256=kJMAxL9ihCd-ULPOfHkqt8TDIMcH6QAOFFw89VplGL0,508
68
+ squirrels/_package_data/base_project/models/sources.yml,sha256=axBHFoJtWjAcklZyibrr7zhyDRATxi9r_bGjI09Kulk,1816
69
+ squirrels/_package_data/base_project/models/builds/build_example.py,sha256=QqNicirKK_JXn44QGrp48Ods5iGfAnoD_PZN5u-YLzQ,1022
70
+ squirrels/_package_data/base_project/models/builds/build_example.sql,sha256=52tqAIzFWJgX13CP4HNeBd5BFy7Z929Z0XxfLgGKthM,508
71
+ squirrels/_package_data/base_project/models/builds/build_example.yml,sha256=kUd6j5u8qU6UD294AowPBHFOb8yYDA_dxWlCIBrPl90,1407
72
+ squirrels/_package_data/base_project/models/dbviews/dbview_example.sql,sha256=RMET1ni2qeEFysxy3lSFHQOxYkfMHvs6G3JUzPhfklU,268
73
+ squirrels/_package_data/base_project/models/dbviews/dbview_example.yml,sha256=lofWnI-fMN6L0q2Yxt6X-4nl6LHv4ZYggYYpRgFQZXI,930
74
+ squirrels/_package_data/base_project/models/federates/federate_example.py,sha256=PvRmdgMaBjatiUclk8L4YvOAENoyb-CnkTJPerLvfLg,1459
75
+ squirrels/_package_data/base_project/models/federates/federate_example.sql,sha256=-i6a4GzHDsiSsKmbsnftnMWh8l2BP447Eh2Qab3KEdo,616
76
+ squirrels/_package_data/base_project/models/federates/federate_example.yml,sha256=FpSVZV7xNSH5iUAXMkyd_jynFxWryutz5Izlgsq3aP0,2279
77
+ squirrels/_package_data/base_project/pyconfigs/connections.py,sha256=IRL9D2HG9pdwp12S896b0Q-hKnFfVck8we1q5sifX10,601
78
+ squirrels/_package_data/base_project/pyconfigs/context.py,sha256=KN5hw23ydMqKLr7IEPHXsBTULZ9a63gECIGTpl1Qg9w,3543
79
+ squirrels/_package_data/base_project/pyconfigs/parameters.py,sha256=-UlrCcuW2NqlNlEnYttQ-S09Lq5IpNkAVP5FNR2e33g,4794
80
+ squirrels/_package_data/base_project/pyconfigs/user.py,sha256=KWfYO7IeGO3qaM9_hJnHMSovG55PvzqHQ6Jc8oKBPcE,1764
81
+ squirrels/_package_data/base_project/seeds/seed_categories.csv,sha256=jppjf1nOIxy7-bi5lJn5CVqmnLfJHHq0ABgp6UqbXnw,104
82
+ squirrels/_package_data/base_project/seeds/seed_categories.yml,sha256=NZ4BVvYYCEq6OnjRLrE_WOMhYsW0BQhRPWOgUchzdp4,435
83
+ squirrels/_package_data/base_project/seeds/seed_subcategories.csv,sha256=Tta1oIgnc2nukNMDlUkIErRKNH_8YT5EPp1A2kQKcow,327
84
+ squirrels/_package_data/base_project/seeds/seed_subcategories.yml,sha256=QTgw8Eld-p6Kntf53FyXyn7-7vKYI7IOJVu-Lr-FHCY,583
85
+ squirrels/_package_data/base_project/tmp/.gitignore,sha256=XImoqcWvJY0C0L_TWCx1ljvqU7qh9fUTJmK4ACCmNFI,13
86
+ squirrels/_package_data/templates/dataset_results.html,sha256=Y-1xtw4ZzHcoW1vfWR1amnFJqFUMvwJDY3eeY7bfKJw,2793
87
+ squirrels/_package_data/templates/oauth_login.html,sha256=LmR7zbjFUYxWIhB9nhIkoGDmqUgEtSXCqo0pKrWqwXw,7108
88
+ squirrels/_package_data/templates/squirrels_studio.html,sha256=M0X3DP5dLRNvO6aJYSasbjWa4NbpsicrxAWV8Ephkyw,785
89
+ squirrels/_schemas/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
90
+ squirrels/_schemas/auth_models.py,sha256=PEDr3jPmaQo7tigjhjyQZ6BYWafbzMUX-jZdo3jRtMU,6392
91
+ squirrels/_schemas/query_param_models.py,sha256=y9ZYa2QLS_1tKN7ODSR0DBOWU-M6cz4niAq5qAAklok,4488
92
+ squirrels/_schemas/response_models.py,sha256=3OVelx3DZmmze7RKmCFzukvHt1nIiLWfmzxSoCNpY50,15218
93
+ squirrels-0.5.0.dist-info/METADATA,sha256=FzhOxJIYUkCseGJJj1xFDAz6temOPRY0JUKMNXgA54E,4555
94
+ squirrels-0.5.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
95
+ squirrels-0.5.0.dist-info/entry_points.txt,sha256=i6vgjhJ3o_cdSFYofFcNY9DFMPr4MIcuwnkskSTXfJc,95
96
+ squirrels-0.5.0.dist-info/licenses/LICENSE,sha256=qqERuumQtQVsMrEXvJHuecJvV2sLxbleEubd_Zk8dY8,11338
97
+ squirrels-0.5.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 1.9.0
2
+ Generator: hatchling 1.27.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ sqrl = squirrels._command_line:main
3
+ squirrels = squirrels._command_line:main
@@ -186,7 +186,7 @@
186
186
  same "printed page" as the copyright notice for easier
187
187
  identification within third-party archives.
188
188
 
189
- Copyright 2024 Tim Huang
189
+ Copyright 2025 Tim Huang
190
190
 
191
191
  Licensed under the Apache License, Version 2.0 (the "License");
192
192
  you may not use this file except in compliance with the License.
@@ -1,85 +0,0 @@
1
- from typing import Type, Optional
2
- from datetime import datetime, timedelta, timezone
3
- from jwt.exceptions import InvalidTokenError
4
- import secrets, jwt
5
-
6
- from . import _utils as u, _constants as c
7
- from .arguments.run_time_args import AuthArgs
8
- from ._py_module import PyModule
9
- from .user_base import User, WrongPassword
10
- from ._environcfg import EnvironConfig
11
- from ._manifest import DatasetScope
12
- from ._connection_set import ConnectionsArgs, ConnectionSet
13
-
14
-
15
- class Authenticator:
16
-
17
- def __init__(self, base_path: str, env_cfg: EnvironConfig, conn_args: ConnectionsArgs, conn_set: ConnectionSet, token_expiry_minutes: int, *, auth_helper = None) -> None:
18
- self.env_cfg = env_cfg
19
- self.conn_args = conn_args
20
- self.conn_set = conn_set
21
- self.token_expiry_minutes = token_expiry_minutes
22
- self.auth_helper = self._get_auth_helper(base_path, default_auth_helper=auth_helper)
23
- self.user_cls: Type[User] = self.auth_helper.get_func_or_class("User", default_attr=User)
24
- self.secret_key = self._get_secret_key()
25
- self.algorithm = "HS256"
26
-
27
- def _get_auth_helper(self, base_path: str, *, default_auth_helper = None):
28
- auth_module_path = u.Path(base_path, c.PYCONFIGS_FOLDER, c.AUTH_FILE)
29
- return PyModule(auth_module_path, default_class=default_auth_helper)
30
-
31
- def _get_secret_key(self) -> str:
32
- secret_key = self.env_cfg.get_secret(c.JWT_SECRET_KEY, default_factory=lambda: secrets.token_hex(32))
33
- return str(secret_key)
34
-
35
- def _get_auth_args(self, username: str, password: str):
36
- connections = self.conn_set.get_engines_as_dict()
37
- return AuthArgs(self.conn_args.proj_vars, self.conn_args.env_vars, self.conn_args._get_credential, connections, username, password)
38
-
39
- def authenticate_user(self, username: str, password: str) -> Optional[User]:
40
- get_user = self.auth_helper.get_func_or_class(c.GET_USER_FUNC, is_required=False)
41
- try:
42
- real_user = get_user(self._get_auth_args(username, password)) if get_user is not None else None
43
- except Exception as e:
44
- raise u.FileExecutionError(f'Failed to run "{c.GET_USER_FUNC}" in {c.AUTH_FILE}', e) from e
45
-
46
- if isinstance(real_user, User):
47
- return real_user
48
-
49
- if not isinstance(real_user, WrongPassword):
50
- fake_users = self.env_cfg.get_users()
51
- if username in fake_users and secrets.compare_digest(fake_users[username].password, password):
52
- fake_user = fake_users[username].model_dump()
53
- fake_user.pop("username", "")
54
- is_internal = fake_user.pop("is_internal", False)
55
- try:
56
- return self.user_cls.Create(username, is_internal=is_internal, **fake_user)
57
- except Exception as e:
58
- raise u.FileExecutionError(f'Failed to create user from User model in {c.AUTH_FILE}', e) from e
59
-
60
- return None
61
-
62
- def create_access_token(self, user: User) -> tuple[str, datetime]:
63
- expire = datetime.now(timezone.utc) + timedelta(minutes=self.token_expiry_minutes)
64
- to_encode = {**vars(user), "exp": expire}
65
- encoded_jwt = jwt.encode(to_encode, self.secret_key, algorithm=self.algorithm)
66
- return encoded_jwt, expire
67
-
68
- def get_user_from_token(self, token: Optional[str]) -> Optional[User]:
69
- if token is not None:
70
- try:
71
- payload: dict = jwt.decode(token, self.secret_key, algorithms=[self.algorithm])
72
- payload.pop("exp")
73
- return self.user_cls._FromDict(payload)
74
- except InvalidTokenError:
75
- return None
76
-
77
- def can_user_access_scope(self, user: Optional[User], scope: DatasetScope) -> bool:
78
- if user is None:
79
- user_level = DatasetScope.PUBLIC
80
- elif not user.is_internal:
81
- user_level = DatasetScope.PROTECTED
82
- else:
83
- user_level = DatasetScope.PRIVATE
84
-
85
- return user_level.value >= scope.value
@@ -1,61 +0,0 @@
1
- from typing import Type, TypeVar, Callable, Coroutine, Any
2
- from dataclasses import dataclass
3
- import inspect, os, time
4
-
5
- from .arguments.run_time_args import DashboardArgs
6
- from ._py_module import PyModule
7
- from . import _constants as c, _utils as u, dashboards as d
8
-
9
- T = TypeVar('T', bound=d.Dashboard)
10
-
11
-
12
- @dataclass
13
- class DashboardFunction:
14
- dashboard_name: str
15
- filepath: str
16
-
17
- @property
18
- def dashboard_func(self) -> Callable[[DashboardArgs], Coroutine[Any, Any, d.Dashboard]]:
19
- if not hasattr(self, '_dashboard_func'):
20
- module = PyModule(self.filepath)
21
- self._dashboard_func = module.get_func_or_class(c.MAIN_FUNC)
22
- return self._dashboard_func
23
-
24
- def get_dashboard_format(self) -> str:
25
- return_type = inspect.signature(self.dashboard_func).return_annotation
26
- assert issubclass(return_type, d.Dashboard), f"Function must return Dashboard type"
27
- if return_type == d.PngDashboard:
28
- return c.PNG
29
- elif return_type == d.HtmlDashboard:
30
- return c.HTML
31
- else:
32
- raise NotImplementedError(f"Dashboard format {return_type} not supported")
33
-
34
- async def get_dashboard(self, args: DashboardArgs, *, dashboard_type: Type[T] = d.Dashboard) -> T:
35
- try:
36
- dashboard = await self.dashboard_func(args)
37
- assert isinstance(dashboard, dashboard_type), f"Function does not return expected Dashboard type: {dashboard_type}"
38
- except (u.InvalidInputError, u.ConfigurationError, u.FileExecutionError) as e:
39
- raise e
40
- except Exception as e:
41
- raise u.FileExecutionError(f'Failed to run "{c.MAIN_FUNC}" function for dashboard "{self.dashboard_name}"', e) from e
42
-
43
- return dashboard
44
-
45
-
46
- class DashboardsIO:
47
-
48
- @classmethod
49
- def load_files(cls, logger: u.Logger, base_path: str) -> dict[str, DashboardFunction]:
50
- start = time.time()
51
-
52
- dashboards_by_name = {}
53
- for dp, _, filenames in os.walk(u.Path(base_path, c.DASHBOARDS_FOLDER)):
54
- for file in filenames:
55
- filepath = os.path.join(dp, file)
56
- file_stem, extension = os.path.splitext(file)
57
- if extension == '.py':
58
- dashboards_by_name[file_stem] = DashboardFunction(file_stem, filepath)
59
-
60
- logger.log_activity_time("loading files for dashboards", start)
61
- return dashboards_by_name
squirrels/_environcfg.py DELETED
@@ -1,84 +0,0 @@
1
- from pathlib import Path
2
- from typing import Any, Callable
3
- from pydantic import BaseModel, Field, field_validator, ValidationError
4
- import os, yaml, time
5
-
6
- from . import _constants as c, _utils as u
7
-
8
- _GLOBAL_SQUIRRELS_CFG_FILE = u.Path(os.path.expanduser('~'), '.squirrels', c.ENV_CONFIG_FILE)
9
-
10
-
11
- class _UserConfig(BaseModel, extra="allow"):
12
- username: str
13
- password: str
14
- is_internal: bool = False
15
-
16
-
17
- class _CredentialsConfig(BaseModel):
18
- username: str
19
- password: str
20
-
21
-
22
- class EnvironConfig(BaseModel):
23
- users: dict[str, _UserConfig] = Field(default_factory=dict)
24
- env_vars: dict[str, str] = Field(default_factory=dict)
25
- credentials: dict[str, _CredentialsConfig] = Field(default_factory=dict)
26
- secrets: dict[str, Any | None] = Field(default_factory=dict)
27
-
28
- @field_validator("users", mode="before")
29
- @classmethod
30
- def inject_username(cls, users: dict[str, dict[str, Any]]) -> dict[str, dict[str, Any]]:
31
- processed_users = {}
32
- for username, user in users.items():
33
- processed_users[username] = {"username": username, **user}
34
- return processed_users
35
-
36
- def get_users(self) -> dict[str, _UserConfig]:
37
- return self.users.copy()
38
-
39
- def get_all_env_vars(self) -> dict[str, str]:
40
- return self.env_vars.copy()
41
-
42
- def get_credential(self, key: str | None) -> tuple[str, str]:
43
- if not key:
44
- return "", ""
45
-
46
- try:
47
- credential = self.credentials[key]
48
- except KeyError as e:
49
- raise u.ConfigurationError(f'No credentials configured for "{key}"') from e
50
-
51
- return credential.username, credential.password
52
-
53
- def get_secret(self, key: str, default_factory: Callable[[], Any]) -> Any:
54
- if self.secrets.get(key) is None:
55
- self.secrets[key] = default_factory()
56
- return self.secrets[key]
57
-
58
-
59
- class EnvironConfigIO:
60
-
61
- @classmethod
62
- def load_from_file(cls, logger: u.Logger, base_path: str) -> EnvironConfig:
63
- start = time.time()
64
- def load_yaml(filename: str | Path) -> dict[str, dict]:
65
- try:
66
- with open(filename, 'r') as f:
67
- return yaml.safe_load(f)
68
- except FileNotFoundError:
69
- return {}
70
-
71
- master_env_config = load_yaml(_GLOBAL_SQUIRRELS_CFG_FILE)
72
- proj_env_config = load_yaml(Path(base_path, c.ENV_CONFIG_FILE))
73
-
74
- for key in proj_env_config:
75
- master_env_config.setdefault(key, {})
76
- master_env_config[key].update(proj_env_config[key])
77
-
78
- try:
79
- env_cfg = EnvironConfig(**master_env_config)
80
- except ValidationError as e:
81
- raise u.ConfigurationError(f"Failed to process {c.ENV_CONFIG_FILE} file. " + str(e)) from e
82
-
83
- logger.log_activity_time(f"loading {c.ENV_CONFIG_FILE} file", start)
84
- return env_cfg
@@ -1,40 +0,0 @@
1
- from typing import Callable, Any
2
- from dataclasses import dataclass
3
-
4
-
5
- @dataclass
6
- class BaseArguments:
7
- _proj_vars: dict[str, Any]
8
- _env_vars: dict[str, Any]
9
-
10
- @property
11
- def proj_vars(self) -> dict[str, Any]:
12
- return self._proj_vars.copy()
13
-
14
- @property
15
- def env_vars(self) -> dict[str, Any]:
16
- return self._env_vars.copy()
17
-
18
-
19
- @dataclass
20
- class ConnectionsArgs(BaseArguments):
21
- _get_credential: Callable[[str | None], tuple[str, str]]
22
-
23
- def get_credential(self, key: str | None) -> tuple[str, str]:
24
- """
25
- Return (username, password) tuple configured for credentials key in env.yaml
26
-
27
- If key is None, returns tuple of empty strings ("", "")
28
-
29
- Arguments:
30
- key: The credentials key
31
-
32
- Returns:
33
- A tuple of 2 strings
34
- """
35
- return self._get_credential(key)
36
-
37
-
38
- @dataclass
39
- class ParametersArgs(BaseArguments):
40
- pass