squirrels 0.5.0rc0__tar.gz → 0.5.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.

Potentially problematic release.


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

Files changed (143) hide show
  1. {squirrels-0.5.0rc0 → squirrels-0.5.1}/.gitignore +2 -1
  2. {squirrels-0.5.0rc0 → squirrels-0.5.1}/PKG-INFO +42 -30
  3. squirrels-0.5.1/README.md +88 -0
  4. squirrels-0.5.1/dateutils/__init__.py +6 -0
  5. squirrels-0.5.1/dateutils/_enums.py +25 -0
  6. squirrels-0.5.0rc0/squirrels/dateutils.py → squirrels-0.5.1/dateutils/_implementation.py +58 -111
  7. squirrels-0.5.1/dateutils/types.py +6 -0
  8. {squirrels-0.5.0rc0 → squirrels-0.5.1}/pyproject.toml +24 -16
  9. squirrels-0.5.1/squirrels/__init__.py +21 -0
  10. squirrels-0.5.1/squirrels/_api_routes/__init__.py +5 -0
  11. squirrels-0.5.1/squirrels/_api_routes/auth.py +271 -0
  12. squirrels-0.5.1/squirrels/_api_routes/base.py +171 -0
  13. squirrels-0.5.1/squirrels/_api_routes/dashboards.py +158 -0
  14. squirrels-0.5.1/squirrels/_api_routes/data_management.py +148 -0
  15. squirrels-0.5.1/squirrels/_api_routes/datasets.py +265 -0
  16. squirrels-0.5.1/squirrels/_api_routes/oauth2.py +298 -0
  17. squirrels-0.5.1/squirrels/_api_routes/project.py +252 -0
  18. squirrels-0.5.1/squirrels/_api_server.py +368 -0
  19. {squirrels-0.5.0rc0/squirrels/arguments → squirrels-0.5.1/squirrels/_arguments}/init_time_args.py +7 -2
  20. {squirrels-0.5.0rc0/squirrels/arguments → squirrels-0.5.1/squirrels/_arguments}/run_time_args.py +13 -35
  21. squirrels-0.5.1/squirrels/_auth.py +959 -0
  22. {squirrels-0.5.0rc0 → squirrels-0.5.1}/squirrels/_command_line.py +81 -41
  23. squirrels-0.5.1/squirrels/_compile_prompts.py +147 -0
  24. {squirrels-0.5.0rc0 → squirrels-0.5.1}/squirrels/_connection_set.py +16 -7
  25. {squirrels-0.5.0rc0 → squirrels-0.5.1}/squirrels/_constants.py +29 -9
  26. squirrels-0.5.0rc0/squirrels/_dashboards_io.py → squirrels-0.5.1/squirrels/_dashboards.py +87 -6
  27. squirrels-0.5.0rc0/squirrels/data_sources.py → squirrels-0.5.1/squirrels/_data_sources.py +94 -87
  28. squirrels-0.5.0rc0/squirrels/dataset_result.py → squirrels-0.5.1/squirrels/_dataset_types.py +2 -4
  29. squirrels-0.5.1/squirrels/_exceptions.py +29 -0
  30. {squirrels-0.5.0rc0 → squirrels-0.5.1}/squirrels/_initializer.py +83 -59
  31. squirrels-0.5.1/squirrels/_logging.py +117 -0
  32. {squirrels-0.5.0rc0 → squirrels-0.5.1}/squirrels/_manifest.py +129 -62
  33. squirrels-0.5.1/squirrels/_model_builder.py +69 -0
  34. {squirrels-0.5.0rc0 → squirrels-0.5.1}/squirrels/_model_configs.py +3 -3
  35. {squirrels-0.5.0rc0 → squirrels-0.5.1}/squirrels/_model_queries.py +1 -1
  36. {squirrels-0.5.0rc0 → squirrels-0.5.1}/squirrels/_models.py +249 -118
  37. {squirrels-0.5.0rc0/squirrels/package_data → squirrels-0.5.1/squirrels/_package_data}/base_project/.env +16 -4
  38. {squirrels-0.5.0rc0/squirrels/package_data → squirrels-0.5.1/squirrels/_package_data}/base_project/.env.example +15 -3
  39. {squirrels-0.5.0rc0/squirrels/package_data → squirrels-0.5.1/squirrels/_package_data}/base_project/connections.yml +4 -3
  40. {squirrels-0.5.0rc0/squirrels/package_data → squirrels-0.5.1/squirrels/_package_data}/base_project/dashboards/dashboard_example.py +4 -4
  41. squirrels-0.5.1/squirrels/_package_data/base_project/dashboards/dashboard_example.yml +22 -0
  42. {squirrels-0.5.0rc0/squirrels/package_data → squirrels-0.5.1/squirrels/_package_data}/base_project/duckdb_init.sql +1 -0
  43. squirrels-0.5.1/squirrels/_package_data/base_project/macros/macros_example.sql +17 -0
  44. {squirrels-0.5.0rc0/squirrels/package_data → squirrels-0.5.1/squirrels/_package_data}/base_project/models/builds/build_example.py +2 -2
  45. {squirrels-0.5.0rc0/squirrels/package_data → squirrels-0.5.1/squirrels/_package_data}/base_project/models/builds/build_example.sql +1 -1
  46. {squirrels-0.5.0rc0/squirrels/package_data → squirrels-0.5.1/squirrels/_package_data}/base_project/models/builds/build_example.yml +2 -0
  47. squirrels-0.5.1/squirrels/_package_data/base_project/models/dbviews/dbview_example.sql +17 -0
  48. squirrels-0.5.1/squirrels/_package_data/base_project/models/dbviews/dbview_example.yml +32 -0
  49. squirrels-0.5.1/squirrels/_package_data/base_project/models/federates/federate_example.py +48 -0
  50. squirrels-0.5.1/squirrels/_package_data/base_project/models/federates/federate_example.sql +21 -0
  51. {squirrels-0.5.0rc0/squirrels/package_data → squirrels-0.5.1/squirrels/_package_data}/base_project/models/federates/federate_example.yml +7 -7
  52. {squirrels-0.5.0rc0/squirrels/package_data → squirrels-0.5.1/squirrels/_package_data}/base_project/models/sources.yml +5 -6
  53. {squirrels-0.5.0rc0/squirrels/package_data → squirrels-0.5.1/squirrels/_package_data}/base_project/parameters.yml +32 -45
  54. squirrels-0.5.1/squirrels/_package_data/base_project/pyconfigs/connections.py +18 -0
  55. {squirrels-0.5.0rc0/squirrels/package_data → squirrels-0.5.1/squirrels/_package_data}/base_project/pyconfigs/context.py +31 -22
  56. squirrels-0.5.1/squirrels/_package_data/base_project/pyconfigs/parameters.py +141 -0
  57. squirrels-0.5.1/squirrels/_package_data/base_project/pyconfigs/user.py +44 -0
  58. {squirrels-0.5.0rc0/squirrels/package_data → squirrels-0.5.1/squirrels/_package_data}/base_project/seeds/seed_categories.yml +1 -1
  59. {squirrels-0.5.0rc0/squirrels/package_data → squirrels-0.5.1/squirrels/_package_data}/base_project/seeds/seed_subcategories.yml +1 -1
  60. squirrels-0.5.1/squirrels/_package_data/base_project/squirrels.yml.j2 +61 -0
  61. squirrels-0.5.1/squirrels/_package_data/templates/dataset_results.html +112 -0
  62. squirrels-0.5.1/squirrels/_package_data/templates/oauth_login.html +271 -0
  63. squirrels-0.5.1/squirrels/_package_data/templates/squirrels_studio.html +20 -0
  64. {squirrels-0.5.0rc0 → squirrels-0.5.1}/squirrels/_parameter_configs.py +76 -55
  65. squirrels-0.5.0rc0/squirrels/parameter_options.py → squirrels-0.5.1/squirrels/_parameter_options.py +1 -1
  66. {squirrels-0.5.0rc0 → squirrels-0.5.1}/squirrels/_parameter_sets.py +53 -45
  67. squirrels-0.5.0rc0/squirrels/parameters.py → squirrels-0.5.1/squirrels/_parameters.py +552 -154
  68. squirrels-0.5.1/squirrels/_project.py +722 -0
  69. {squirrels-0.5.0rc0 → squirrels-0.5.1}/squirrels/_py_module.py +3 -2
  70. squirrels-0.5.1/squirrels/_request_context.py +33 -0
  71. squirrels-0.5.1/squirrels/_schemas/__init__.py +0 -0
  72. squirrels-0.5.1/squirrels/_schemas/auth_models.py +167 -0
  73. squirrels-0.5.1/squirrels/_schemas/query_param_models.py +75 -0
  74. squirrels-0.5.0rc0/squirrels/_api_response_models.py → squirrels-0.5.1/squirrels/_schemas/response_models.py +48 -18
  75. {squirrels-0.5.0rc0 → squirrels-0.5.1}/squirrels/_seeds.py +1 -1
  76. {squirrels-0.5.0rc0 → squirrels-0.5.1}/squirrels/_sources.py +23 -19
  77. {squirrels-0.5.0rc0 → squirrels-0.5.1}/squirrels/_utils.py +121 -39
  78. {squirrels-0.5.0rc0 → squirrels-0.5.1}/squirrels/_version.py +1 -1
  79. squirrels-0.5.1/squirrels/arguments.py +7 -0
  80. squirrels-0.5.1/squirrels/auth.py +4 -0
  81. squirrels-0.5.1/squirrels/connections.py +3 -0
  82. squirrels-0.5.1/squirrels/dashboards.py +3 -0
  83. squirrels-0.5.1/squirrels/data_sources.py +14 -0
  84. squirrels-0.5.1/squirrels/parameter_options.py +13 -0
  85. squirrels-0.5.1/squirrels/parameters.py +14 -0
  86. squirrels-0.5.1/squirrels/types.py +16 -0
  87. squirrels-0.5.0rc0/.cursorignore +0 -2
  88. squirrels-0.5.0rc0/.github/workflows/python-publish.yml +0 -45
  89. squirrels-0.5.0rc0/README.md +0 -78
  90. squirrels-0.5.0rc0/database_elt/expenses/.gitignore +0 -1
  91. squirrels-0.5.0rc0/database_elt/expenses/create-expenses.py +0 -90
  92. squirrels-0.5.0rc0/database_elt/expenses/create-lookups.py +0 -55
  93. squirrels-0.5.0rc0/database_elt/seattle_weather/create_db.py +0 -13
  94. squirrels-0.5.0rc0/database_elt/seattle_weather/seattle-weather.csv +0 -1462
  95. squirrels-0.5.0rc0/squirrels/__init__.py +0 -23
  96. squirrels-0.5.0rc0/squirrels/_api_server.py +0 -904
  97. squirrels-0.5.0rc0/squirrels/_auth.py +0 -451
  98. squirrels-0.5.0rc0/squirrels/_exceptions.py +0 -57
  99. squirrels-0.5.0rc0/squirrels/_model_builder.py +0 -111
  100. squirrels-0.5.0rc0/squirrels/_project.py +0 -561
  101. squirrels-0.5.0rc0/squirrels/dashboards.py +0 -82
  102. squirrels-0.5.0rc0/squirrels/package_data/base_project/dashboards/dashboard_example.yml +0 -22
  103. squirrels-0.5.0rc0/squirrels/package_data/base_project/macros/macros_example.sql +0 -15
  104. squirrels-0.5.0rc0/squirrels/package_data/base_project/models/dbviews/dbview_example.sql +0 -12
  105. squirrels-0.5.0rc0/squirrels/package_data/base_project/models/dbviews/dbview_example.yml +0 -26
  106. squirrels-0.5.0rc0/squirrels/package_data/base_project/models/federates/federate_example.py +0 -44
  107. squirrels-0.5.0rc0/squirrels/package_data/base_project/models/federates/federate_example.sql +0 -17
  108. squirrels-0.5.0rc0/squirrels/package_data/base_project/pyconfigs/connections.py +0 -14
  109. squirrels-0.5.0rc0/squirrels/package_data/base_project/pyconfigs/parameters.py +0 -93
  110. squirrels-0.5.0rc0/squirrels/package_data/base_project/pyconfigs/user.py +0 -23
  111. squirrels-0.5.0rc0/squirrels/package_data/base_project/squirrels.yml.j2 +0 -71
  112. squirrels-0.5.0rc0/tests/_auth_test.py +0 -166
  113. squirrels-0.5.0rc0/tests/_connection_set_test.py +0 -46
  114. squirrels-0.5.0rc0/tests/_manifest_test.py +0 -290
  115. squirrels-0.5.0rc0/tests/_model_builder_test.py +0 -168
  116. squirrels-0.5.0rc0/tests/_model_configs_test.py +0 -16
  117. squirrels-0.5.0rc0/tests/_models_basic_test.py +0 -152
  118. squirrels-0.5.0rc0/tests/_models_test.py +0 -167
  119. squirrels-0.5.0rc0/tests/_seeds_test.py +0 -124
  120. squirrels-0.5.0rc0/tests/_sources_test.py +0 -151
  121. squirrels-0.5.0rc0/tests/_utils_test.py +0 -22
  122. squirrels-0.5.0rc0/tests/arguments/run_time_args_test.py +0 -16
  123. squirrels-0.5.0rc0/tests/conftest.py +0 -19
  124. squirrels-0.5.0rc0/tests/data_sources_test.py +0 -206
  125. squirrels-0.5.0rc0/tests/dateutils_test.py +0 -132
  126. squirrels-0.5.0rc0/tests/parameter_configs_tests/_parameter_configs_test.py +0 -184
  127. squirrels-0.5.0rc0/tests/parameter_configs_tests/_parameter_sets_test.py +0 -237
  128. squirrels-0.5.0rc0/tests/parameter_configs_tests/conftest.py +0 -111
  129. squirrels-0.5.0rc0/tests/parameter_options_test.py +0 -122
  130. squirrels-0.5.0rc0/tests/parameters_test.py +0 -386
  131. squirrels-0.5.0rc0/uv.lock +0 -2055
  132. {squirrels-0.5.0rc0 → squirrels-0.5.1}/LICENSE +0 -0
  133. {squirrels-0.5.0rc0/tests → squirrels-0.5.1/squirrels/_arguments}/__init__.py +0 -0
  134. {squirrels-0.5.0rc0/squirrels/package_data → squirrels-0.5.1/squirrels/_package_data}/base_project/assets/expenses.db +0 -0
  135. {squirrels-0.5.0rc0/squirrels/package_data → squirrels-0.5.1/squirrels/_package_data}/base_project/assets/weather.db +0 -0
  136. {squirrels-0.5.0rc0/squirrels/package_data → squirrels-0.5.1/squirrels/_package_data}/base_project/docker/.dockerignore +0 -0
  137. {squirrels-0.5.0rc0/squirrels/package_data → squirrels-0.5.1/squirrels/_package_data}/base_project/docker/Dockerfile +0 -0
  138. {squirrels-0.5.0rc0/squirrels/package_data → squirrels-0.5.1/squirrels/_package_data}/base_project/docker/compose.yml +0 -0
  139. /squirrels-0.5.0rc0/squirrels/package_data/base_project/.gitignore → /squirrels-0.5.1/squirrels/_package_data/base_project/gitignore +0 -0
  140. {squirrels-0.5.0rc0/squirrels/package_data → squirrels-0.5.1/squirrels/_package_data}/base_project/seeds/seed_categories.csv +0 -0
  141. {squirrels-0.5.0rc0/squirrels/package_data → squirrels-0.5.1/squirrels/_package_data}/base_project/seeds/seed_subcategories.csv +0 -0
  142. {squirrels-0.5.0rc0/squirrels/package_data → squirrels-0.5.1/squirrels/_package_data}/base_project/tmp/.gitignore +0 -0
  143. {squirrels-0.5.0rc0 → squirrels-0.5.1}/squirrels/_package_loader.py +0 -0
@@ -1,7 +1,8 @@
1
1
  sandbox*
2
2
  playground*
3
3
  site/
4
- .venv/
4
+ .venv/
5
+ TODOS.md
5
6
 
6
7
  # pyenv files
7
8
  .python-version
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: squirrels
3
- Version: 0.5.0rc0
3
+ Version: 0.5.1
4
4
  Summary: Squirrels - API Framework for Data Analytics
5
5
  Project-URL: Homepage, https://squirrels-analytics.github.io
6
6
  Project-URL: Repository, https://github.com/squirrels-analytics/squirrels
@@ -13,26 +13,28 @@ Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
13
13
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
14
14
  Classifier: Typing :: Typed
15
15
  Requires-Python: ~=3.10
16
+ Requires-Dist: authlib<2,>=1.5.2
16
17
  Requires-Dist: bcrypt<5,>=4.0.1
17
- Requires-Dist: cachetools<6,>=5.3.2
18
- Requires-Dist: duckdb<2,>=1.1.3
18
+ Requires-Dist: cachetools>=5.3.2
19
+ Requires-Dist: duckdb<2,>=1.4.0
19
20
  Requires-Dist: fastapi<1,>=0.112.1
20
21
  Requires-Dist: gitpython<4,>=3.1.41
21
22
  Requires-Dist: inquirer<4,>=3.2.1
23
+ Requires-Dist: itsdangerous<3,>=2.2.0
22
24
  Requires-Dist: jinja2<4,>=3.1.3
25
+ Requires-Dist: libpass<2,>=1.9.0
23
26
  Requires-Dist: matplotlib<4,>=3.8.3
24
- Requires-Dist: networkx<4,>=3.2.1
27
+ Requires-Dist: mcp>=1.13.1
25
28
  Requires-Dist: pandas<3,>=2.1.4
26
- Requires-Dist: passlib<2,>=1.7.4
27
29
  Requires-Dist: polars<2,>=1.14.0
28
- Requires-Dist: pyarrow<19,>=18.0.0
30
+ Requires-Dist: pyarrow>=19.0.1
29
31
  Requires-Dist: pydantic<3,>=2.8.2
30
32
  Requires-Dist: pyjwt<3,>=2.8.0
31
33
  Requires-Dist: python-dotenv<2,>=1.0.1
32
34
  Requires-Dist: python-multipart<1,>=0.0.9
33
35
  Requires-Dist: pyyaml<7,>=6.0.1
34
36
  Requires-Dist: sqlalchemy<3,>=2.0.25
35
- Requires-Dist: sqlglot<26,>=25.32.1
37
+ Requires-Dist: sqlglot>=26.12.1
36
38
  Requires-Dist: uvicorn<1,>=0.30.6
37
39
  Description-Content-Type: text/markdown
38
40
 
@@ -48,22 +50,38 @@ Squirrels is an API framework that lets you create REST APIs for dynamic data an
48
50
 
49
51
  - [Main Features](#main-features)
50
52
  - [License](#license)
51
- - [Contributing to squirrels](#contributing-to-squirrels)
53
+ - [Contributing to Squirrels](#contributing-to-squirrels)
52
54
  - [Setup](#setup)
53
55
  - [Testing](#testing)
54
56
  - [Project Structure](#project-structure)
55
57
 
56
58
  ## Main Features
57
59
 
58
- Here are a few of the things that squirrels can do:
60
+ Here are a few of the things that Squirrels can do:
59
61
 
60
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`).
61
63
  - Configure API routes for datasets (in `squirrels.yml`) without writing code.
62
64
  - Configure parameter widgets (types include single-select, multi-select, date, number, etc.) for your datasets (in `parameters.py`).
63
- - Use Jinja SQL templates (just like dbt!) or python functions (that return a Python dataframe such as polars or pandas) to define dynamic query logic based on parameter selections.
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.
64
66
  - Query multiple databases and join the results together in a final view in one API endpoint/dataset!
65
- - Test your API endpoints with an interactive UI or by a command line that generates rendered sql queries and results (for a given set of parameter selections).
66
- - Define authentication logic (in `auth.py`) and authorize privacy scope per dataset (in `squirrels.yml`). The user's attributes can even be used in your query logic!
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
+ ## Quick Start
72
+
73
+ In a new virtual environment, install `squirrels`. Then, in your project directory, activate the virtual environment and run the following commands:
74
+
75
+ ```bash
76
+ sqrl new --use-defaults --curr-dir
77
+ sqrl build
78
+ ```
79
+
80
+ To run the API server, simply run:
81
+
82
+ ```bash
83
+ sqrl run
84
+ ```
67
85
 
68
86
  ## License
69
87
 
@@ -71,46 +89,40 @@ Squirrels is released under the Apache 2.0 license.
71
89
 
72
90
  See the file LICENSE for more details.
73
91
 
74
- ## Contributing to squirrels
92
+ ## Contributing to Squirrels
75
93
 
76
- 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.
94
+ 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.
77
95
 
78
96
  ### Setup
79
97
 
80
- This project requires python version 3.10 or above to be installed. It also uses the python build tool `poetry`. Information on setting up poetry can be found at: https://python-poetry.org/docs/.
98
+ 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/.
81
99
 
82
- Then, to install all dependencies, run:
100
+ Then, to install all dependencies in a virtual environment, run:
83
101
 
84
- ```
85
- poetry install
102
+ ```bash
103
+ uv sync
86
104
  ```
87
105
 
88
- And activate the virtual environment created by poetry with:
106
+ And activate the virtual environment with:
89
107
 
90
- ```
91
- poetry shell
108
+ ```bash
109
+ source .venv/bin/activate
92
110
  ```
93
111
 
94
- To confirm that the setup worked, run the following to show the help page for all squirrels CLI commands:
112
+ To confirm that the setup worked, run the following to show the help page for all Squirrels CLI commands:
95
113
 
96
114
  ```bash
97
115
  sqrl -h
98
116
  ```
99
117
 
100
- You can enter `exit` to exit the virtual environment shell. You can also run `poetry run sqrl -h` to run squirrels commands without activating the virtual environment.
101
-
102
118
  ### Testing
103
119
 
104
- In poetry's virtual environment, run `pytest`.
120
+ Run `uv run pytest`. Or if you have the virtual environment activated, simply run `pytest`.
105
121
 
106
122
  ### Project Structure
107
123
 
108
124
  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.
109
125
 
110
- To understand what a specific squirrels command is doing, start from the `_command_line.py` file as your entry point.
126
+ To understand what a specific Squirrels command is doing, start from the `_command_line.py` file as your entry point.
111
127
 
112
128
  The library version is maintained in both the `pyproject.toml` and the `squirrels/_version.py` files.
113
-
114
- When a user initializes a squirrels project using `sqrl init`, the files are copied from the `squirrels/package_data/base_project` folder. The contents in the `database` subfolder were constructed from the scripts in the `database_elt` folder.
115
-
116
- For the Squirrels UI activated by `sqrl run`, the HTML, CSS, and Javascript files can be found in the `static` and `templates` subfolders of `squirrels/package_data`. The CSS and Javascript files are minified and built from the source files in this project: https://github.com/squirrels-analytics/squirrels-testing-ui.
@@ -0,0 +1,88 @@
1
+ # Squirrels
2
+
3
+ Squirrels is an API framework that lets you create REST APIs for dynamic data analytics!
4
+
5
+ **Documentation**: <a href="https://squirrels-analytics.github.io/" target="_blank">https://squirrels-analytics.github.io/</a>
6
+
7
+ **Source Code**: <a href="https://github.com/squirrels-analytics/squirrels" target="_blank">https://github.com/squirrels-analytics/squirrels</a>
8
+
9
+ ## Table of Contents
10
+
11
+ - [Main Features](#main-features)
12
+ - [License](#license)
13
+ - [Contributing to Squirrels](#contributing-to-squirrels)
14
+ - [Setup](#setup)
15
+ - [Testing](#testing)
16
+ - [Project Structure](#project-structure)
17
+
18
+ ## Main Features
19
+
20
+ Here are a few of the things that Squirrels can do:
21
+
22
+ - Connect to any database by specifying its SQLAlchemy url (in `squirrels.yml`) or by using its native connector library in python (in `connections.py`).
23
+ - Configure API routes for datasets (in `squirrels.yml`) without writing code.
24
+ - Configure parameter widgets (types include single-select, multi-select, date, number, etc.) for your datasets (in `parameters.py`).
25
+ - 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.
26
+ - Query multiple databases and join the results together in a final view in one API endpoint/dataset!
27
+ - 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).
28
+ - 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!
29
+ - Serve dataset metadata and results to AI agents via MCP (Model Context Protocol)
30
+
31
+ ## Quick Start
32
+
33
+ In a new virtual environment, install `squirrels`. Then, in your project directory, activate the virtual environment and run the following commands:
34
+
35
+ ```bash
36
+ sqrl new --use-defaults --curr-dir
37
+ sqrl build
38
+ ```
39
+
40
+ To run the API server, simply run:
41
+
42
+ ```bash
43
+ sqrl run
44
+ ```
45
+
46
+ ## License
47
+
48
+ Squirrels is released under the Apache 2.0 license.
49
+
50
+ See the file LICENSE for more details.
51
+
52
+ ## Contributing to Squirrels
53
+
54
+ 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.
55
+
56
+ ### Setup
57
+
58
+ 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/.
59
+
60
+ Then, to install all dependencies in a virtual environment, run:
61
+
62
+ ```bash
63
+ uv sync
64
+ ```
65
+
66
+ And activate the virtual environment with:
67
+
68
+ ```bash
69
+ source .venv/bin/activate
70
+ ```
71
+
72
+ To confirm that the setup worked, run the following to show the help page for all Squirrels CLI commands:
73
+
74
+ ```bash
75
+ sqrl -h
76
+ ```
77
+
78
+ ### Testing
79
+
80
+ Run `uv run pytest`. Or if you have the virtual environment activated, simply run `pytest`.
81
+
82
+ ### Project Structure
83
+
84
+ 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.
85
+
86
+ To understand what a specific Squirrels command is doing, start from the `_command_line.py` file as your entry point.
87
+
88
+ The library version is maintained in both the `pyproject.toml` and the `squirrels/_version.py` files.
@@ -0,0 +1,6 @@
1
+ from ._enums import DayOfWeekEnum, MonthEnum
2
+ from ._implementation import (
3
+ DayIdxOfMonthsCycle, DayIdxOfYear, DayIdxOfQuarter, DayIdxOfMonth, DayIdxOfWeek,
4
+ OffsetYears, OffsetMonths, OffsetWeeks, OffsetDays,
5
+ DateModPipeline, DateStringModifier, TimestampModifier
6
+ )
@@ -0,0 +1,25 @@
1
+ from enum import Enum
2
+
3
+
4
+ class DayOfWeekEnum(Enum):
5
+ Sunday = 0
6
+ Monday = 1
7
+ Tuesday = 2
8
+ Wednesday = 3
9
+ Thursday = 4
10
+ Friday = 5
11
+ Saturday = 6
12
+
13
+ class MonthEnum(Enum):
14
+ January = 1
15
+ February = 2
16
+ March = 3
17
+ April = 4
18
+ May = 5
19
+ June = 6
20
+ July = 7
21
+ August = 8
22
+ September = 9
23
+ October = 10
24
+ November = 11
25
+ December = 12
@@ -3,33 +3,8 @@ from dataclasses import dataclass
3
3
  from datetime import date as Date, datetime
4
4
  from dateutil.relativedelta import relativedelta
5
5
  from abc import ABCMeta, abstractmethod
6
- from enum import Enum
7
-
8
- from . import _utils as u
9
-
10
-
11
- class DayOfWeek(Enum):
12
- Sunday = 0
13
- Monday = 1
14
- Tuesday = 2
15
- Wednesday = 3
16
- Thursday = 4
17
- Friday = 5
18
- Saturday = 6
19
-
20
- class Month(Enum):
21
- January = 1
22
- February = 2
23
- March = 3
24
- April = 4
25
- May = 5
26
- June = 6
27
- July = 7
28
- August = 8
29
- September = 9
30
- October = 10
31
- November = 11
32
- December = 12
6
+
7
+ from ._enums import DayOfWeekEnum, MonthEnum
33
8
 
34
9
 
35
10
  class DateModifier(metaclass=ABCMeta):
@@ -54,20 +29,21 @@ class DateModifier(metaclass=ABCMeta):
54
29
  return datetype(year, month, day)
55
30
 
56
31
 
57
- class _DayIdxOfCalendarUnit(DateModifier):
32
+ @dataclass
33
+ class DayIdxOfCalendarUnit(DateModifier):
58
34
  """
59
35
  Interface for adjusting a date to some day of calendar unit
60
36
  """
61
- def __init__(self, idx: int) -> None:
62
- super().__init__()
63
- self.idx = idx
37
+ idx: int
38
+
39
+ def __post_init__(self) -> None:
64
40
  if self.idx == 0:
65
- raise u.ConfigurationError(f"For constructors of class names that start with DayIdxOf_, idx cannot be zero")
41
+ raise ValueError(f"For constructors of class names that start with DayIdxOf_, idx cannot be zero")
66
42
  self.incr = self.idx - 1 if self.idx > 0 else self.idx
67
43
 
68
44
 
69
45
  @dataclass
70
- class DayIdxOfMonthsCycle(_DayIdxOfCalendarUnit):
46
+ class DayIdxOfMonthsCycle(DayIdxOfCalendarUnit):
71
47
  """
72
48
  DateModifier class to get the idx-th day of a cycle of months for an input date
73
49
 
@@ -76,23 +52,21 @@ class DayIdxOfMonthsCycle(_DayIdxOfCalendarUnit):
76
52
  num_months_in_cycle: 2 for one 6th of year, 3 for Quarter, 4 for one 3rd of year, 6 for half year, 12 for full year. Must fit evenly in 12
77
53
  first_month_of_cycle: The first month of months cycle of year. Default is January
78
54
  """
79
- _num_months_in_cycle: int
80
- _first_month_of_cycle: Month
55
+ num_months_in_cycle: int
56
+ first_month_of_cycle: MonthEnum = MonthEnum.January
81
57
 
82
- def __init__(self, idx: int, num_months_in_cycle: int, first_month_of_cycle: Month = Month.January) -> None:
83
- super().__init__(idx)
84
- self._num_months_in_cycle = num_months_in_cycle
85
- self._first_month_of_cycle = first_month_of_cycle
86
- if 12 % self._num_months_in_cycle != 0:
87
- raise u.ConfigurationError(f"Value X must fit evenly in 12")
88
- self.first_month_of_first_cycle = (self._first_month_of_cycle.value - 1) % self._num_months_in_cycle + 1
58
+ def __post_init__(self) -> None:
59
+ super().__post_init__()
60
+ if 12 % self.num_months_in_cycle != 0:
61
+ raise ValueError(f"Argument 'num_months_in_cycle' must fit evenly in 12")
62
+ self.first_month_of_first_cycle = (self.first_month_of_cycle.value - 1) % self.num_months_in_cycle + 1
89
63
 
90
64
  def modify(self, date: Date) -> Date:
91
- current_cycle = (date.month - self.first_month_of_first_cycle) % 12 // self._num_months_in_cycle
92
- first_month_of_curr_cycle = current_cycle * self._num_months_in_cycle + self.first_month_of_first_cycle
65
+ current_cycle = (date.month - self.first_month_of_first_cycle) % 12 // self.num_months_in_cycle
66
+ first_month_of_curr_cycle = current_cycle * self.num_months_in_cycle + self.first_month_of_first_cycle
93
67
  year = date.year if date.month >= first_month_of_curr_cycle else date.year - 1
94
68
  first_day = self._get_date(type(date), year, first_month_of_curr_cycle, 1)
95
- ref_date = first_day if self.idx > 0 else first_day + relativedelta(months=self._num_months_in_cycle)
69
+ ref_date = first_day if self.idx > 0 else first_day + relativedelta(months=self.num_months_in_cycle)
96
70
  return ref_date + relativedelta(days=self.incr)
97
71
 
98
72
 
@@ -106,7 +80,7 @@ class DayIdxOfYear(DayIdxOfMonthsCycle):
106
80
  first_month_of_year: The first month of year. Default is January
107
81
  """
108
82
 
109
- def __init__(self, idx: int, first_month_of_year: Month = Month.January):
83
+ def __init__(self, idx: int, first_month_of_year: MonthEnum = MonthEnum.January):
110
84
  super().__init__(idx, num_months_in_cycle=12, first_month_of_cycle=first_month_of_year)
111
85
 
112
86
 
@@ -120,12 +94,12 @@ class DayIdxOfQuarter(DayIdxOfMonthsCycle):
120
94
  first_month_of_quarter: The first month of first quarter. Default is January
121
95
  """
122
96
 
123
- def __init__(self, idx: int, first_month_of_quarter: Month = Month.January):
97
+ def __init__(self, idx: int, first_month_of_quarter: MonthEnum = MonthEnum.January):
124
98
  super().__init__(idx, num_months_in_cycle=3, first_month_of_cycle=first_month_of_quarter)
125
99
 
126
100
 
127
101
  @dataclass
128
- class DayIdxOfMonth(_DayIdxOfCalendarUnit):
102
+ class DayIdxOfMonth(DayIdxOfCalendarUnit):
129
103
  """
130
104
  DateModifier class to get the idx-th day of month of an input date
131
105
 
@@ -133,9 +107,6 @@ class DayIdxOfMonth(_DayIdxOfCalendarUnit):
133
107
  idx: 1 for first, 2 for second, etc. Or, -1 for last, -2 for second last, etc. Must not be 0
134
108
  """
135
109
 
136
- def __init__(self, idx: int) -> None:
137
- super().__init__(idx)
138
-
139
110
  def modify(self, date: Date) -> Date:
140
111
  first_day = self._get_date(type(date), date.year, date.month, 1)
141
112
  ref_date = first_day if self.idx > 0 else first_day + relativedelta(months=1)
@@ -143,7 +114,7 @@ class DayIdxOfMonth(_DayIdxOfCalendarUnit):
143
114
 
144
115
 
145
116
  @dataclass
146
- class DayIdxOfWeek(_DayIdxOfCalendarUnit):
117
+ class DayIdxOfWeek(DayIdxOfCalendarUnit):
147
118
  """
148
119
  DateModifier class to get the idx-th day of week of an input date
149
120
 
@@ -151,12 +122,11 @@ class DayIdxOfWeek(_DayIdxOfCalendarUnit):
151
122
  idx: 1 for first, 2 for second, etc. Or, -1 for last, -2 for second last, etc. Must not be 0
152
123
  first_day_of_week: The day of week identified as the "first". Default is Monday
153
124
  """
154
- _first_day_of_week: DayOfWeek
125
+ first_day_of_week: DayOfWeekEnum = DayOfWeekEnum.Monday
155
126
 
156
- def __init__(self, idx: int, first_day_of_week: DayOfWeek = DayOfWeek.Monday) -> None:
157
- super().__init__(idx)
158
- self._first_day_of_week = first_day_of_week
159
- self.first_dow_num = self._first_day_of_week.value
127
+ def __post_init__(self) -> None:
128
+ super().__post_init__()
129
+ self.first_dow_num = self.first_day_of_week.value
160
130
 
161
131
  def modify(self, date: Date) -> Date:
162
132
  distance_from_first_day = (1 + date.weekday() - self.first_dow_num) % 7
@@ -164,17 +134,16 @@ class DayIdxOfWeek(_DayIdxOfCalendarUnit):
164
134
  return date + relativedelta(days=total_incr)
165
135
 
166
136
 
167
- class _OffsetUnits(DateModifier):
137
+ @dataclass
138
+ class OffsetUnits(DateModifier):
168
139
  """
169
140
  Abstract DateModifier class to offset an input date by some number of some calendar unit
170
141
  """
171
- def __init__(self, offset: int) -> None:
172
- super().__init__()
173
- self.offset = offset
142
+ offset: int
174
143
 
175
144
 
176
145
  @dataclass
177
- class OffsetYears(_OffsetUnits):
146
+ class OffsetYears(OffsetUnits):
178
147
  """
179
148
  DateModifier class to offset an input date by some number of years
180
149
 
@@ -182,15 +151,12 @@ class OffsetYears(_OffsetUnits):
182
151
  offset: The number of years to offset the input date.
183
152
  """
184
153
 
185
- def __init__(self, offset: int) -> None:
186
- super().__init__(offset)
187
-
188
154
  def modify(self, date: Date) -> Date:
189
155
  return date + relativedelta(years=self.offset)
190
156
 
191
157
 
192
158
  @dataclass
193
- class OffsetMonths(_OffsetUnits):
159
+ class OffsetMonths(OffsetUnits):
194
160
  """
195
161
  DateModifier class to offset an input date by some number of months
196
162
 
@@ -198,15 +164,12 @@ class OffsetMonths(_OffsetUnits):
198
164
  offset: The number of months to offset the input date.
199
165
  """
200
166
 
201
- def __init__(self, offset: int) -> None:
202
- super().__init__(offset)
203
-
204
167
  def modify(self, date: Date) -> Date:
205
168
  return date + relativedelta(months=self.offset)
206
169
 
207
170
 
208
171
  @dataclass
209
- class OffsetWeeks(_OffsetUnits):
172
+ class OffsetWeeks(OffsetUnits):
210
173
  """
211
174
  DateModifier class to offset an input date by some number of weeks
212
175
 
@@ -214,15 +177,12 @@ class OffsetWeeks(_OffsetUnits):
214
177
  offset: The number of weeks to offset the input date.
215
178
  """
216
179
 
217
- def __init__(self, offset: int) -> None:
218
- super().__init__(offset)
219
-
220
180
  def modify(self, date: Date) -> Date:
221
181
  return date + relativedelta(weeks=self.offset)
222
182
 
223
183
 
224
184
  @dataclass
225
- class OffsetDays(_OffsetUnits):
185
+ class OffsetDays(OffsetUnits):
226
186
  """
227
187
  DateModifier class to offset an input date by some number of days
228
188
 
@@ -230,9 +190,6 @@ class OffsetDays(_OffsetUnits):
230
190
  offset: The number of days to offset the input date.
231
191
  """
232
192
 
233
- def __init__(self, offset: int) -> None:
234
- super().__init__(offset)
235
-
236
193
  def modify(self, date: Date) -> Date:
237
194
  return date + relativedelta(days=self.offset)
238
195
 
@@ -245,14 +202,10 @@ class DateModPipeline(DateModifier):
245
202
  Attributes:
246
203
  modifiers: The list of DateModifier's to apply in sequence.
247
204
  """
248
- _date_modifiers: Sequence[DateModifier]
205
+ date_modifiers: Sequence[DateModifier]
249
206
 
250
- def __init__(self, date_modifiers: Sequence[DateModifier]) -> None:
251
- super().__init__()
252
- self._date_modifiers = tuple(date_modifiers)
253
-
254
207
  def modify(self, date: Date) -> Date:
255
- for modifier in self._date_modifiers:
208
+ for modifier in self.date_modifiers:
256
209
  date = modifier.modify(date)
257
210
  return date
258
211
 
@@ -267,7 +220,7 @@ class DateModPipeline(DateModifier):
267
220
  Returns:
268
221
  A new sequence of DateModifier
269
222
  """
270
- joined_modifiers = tuple(self._date_modifiers) + tuple(date_modifiers)
223
+ joined_modifiers = tuple(self.date_modifiers) + tuple(date_modifiers)
271
224
  return joined_modifiers
272
225
 
273
226
  def with_more_modifiers(self, date_modifiers: Sequence[DateModifier]):
@@ -300,9 +253,9 @@ class DateModPipeline(DateModifier):
300
253
  Returns:
301
254
  A list of datetime objects
302
255
  """
303
- assert isinstance(step, _OffsetUnits)
256
+ assert isinstance(step, OffsetUnits)
304
257
  if step.offset == 0:
305
- raise u.ConfigurationError(f"The length of 'step' must not be zero")
258
+ raise ValueError(f"The length of 'step' must not be zero")
306
259
 
307
260
  output: Sequence[Date] = []
308
261
  end_date = self.modify(start_date)
@@ -315,12 +268,15 @@ class DateModPipeline(DateModifier):
315
268
  return output
316
269
 
317
270
 
318
- class _DateRepresentationModifier(metaclass=ABCMeta):
271
+ @dataclass
272
+ class DateRepresentationModifier(metaclass=ABCMeta):
319
273
  """
320
274
  Abstract class for modifying other representations of dates (such as string or unix timestemp)
321
275
  """
322
- def __init__(self, date_modifiers: Sequence[DateModifier]):
323
- self.date_modifier = DateModPipeline(date_modifiers)
276
+ date_modifiers: Sequence[DateModifier]
277
+
278
+ def __post_init__(self) -> None:
279
+ self.date_mod_pipeline = DateModPipeline(self.date_modifiers)
324
280
 
325
281
  @abstractmethod
326
282
  def with_more_modifiers(self, date_modifiers: Sequence[DateModifier]):
@@ -328,7 +284,7 @@ class _DateRepresentationModifier(metaclass=ABCMeta):
328
284
 
329
285
 
330
286
  @dataclass
331
- class DateStringModifier(_DateRepresentationModifier):
287
+ class DateStringModifier(DateRepresentationModifier):
332
288
  """
333
289
  Class to modify a string representation of a date given a DateModifier
334
290
 
@@ -336,12 +292,7 @@ class DateStringModifier(_DateRepresentationModifier):
336
292
  date_modifier: The DateModifier to apply on datetime objects
337
293
  date_format: Format of the output date string. Default is '%Y-%m-%d'
338
294
  """
339
- _date_modifiers: Sequence[DateModifier]
340
- _date_format: str
341
-
342
- def __init__(self, date_modifiers: Sequence[DateModifier], date_format: str = '%Y-%m-%d'):
343
- super().__init__(date_modifiers)
344
- self._date_format = date_format
295
+ date_format: str = '%Y-%m-%d'
345
296
 
346
297
  def with_more_modifiers(self, date_modifiers: Sequence[DateModifier]):
347
298
  """
@@ -353,11 +304,11 @@ class DateStringModifier(_DateRepresentationModifier):
353
304
  Returns:
354
305
  A new DateStringModifier
355
306
  """
356
- joined_modifiers = self.date_modifier.get_joined_modifiers(date_modifiers)
357
- return DateStringModifier(joined_modifiers, self._date_format)
307
+ joined_modifiers = self.date_mod_pipeline.get_joined_modifiers(date_modifiers)
308
+ return DateStringModifier(joined_modifiers, self.date_format)
358
309
 
359
310
  def _get_input_date_obj(self, date_str: str, input_format: str | None = None) -> Date:
360
- input_format = self._date_format if input_format is None else input_format
311
+ input_format = self.date_format if input_format is None else input_format
361
312
  return datetime.strptime(date_str, input_format).date()
362
313
 
363
314
  def modify(self, date_str: str, input_format: str | None = None) -> str:
@@ -372,7 +323,7 @@ class DateStringModifier(_DateRepresentationModifier):
372
323
  The resulting date string
373
324
  """
374
325
  date_obj = self._get_input_date_obj(date_str, input_format)
375
- return self.date_modifier.modify(date_obj).strftime(self._date_format)
326
+ return self.date_mod_pipeline.modify(date_obj).strftime(self.date_format)
376
327
 
377
328
  def get_date_list(self, start_date_str: str, step: DateModifier, input_format: str | None = None) -> Sequence[str]:
378
329
  """
@@ -392,14 +343,14 @@ class DateStringModifier(_DateRepresentationModifier):
392
343
  Returns:
393
344
  A list of date strings
394
345
  """
395
- assert isinstance(step, _OffsetUnits)
346
+ assert isinstance(step, OffsetUnits)
396
347
  curr_date = self._get_input_date_obj(start_date_str, input_format)
397
- output = self.date_modifier.get_date_list(curr_date, step)
398
- return [x.strftime(self._date_format) for x in output]
348
+ output = self.date_mod_pipeline.get_date_list(curr_date, step)
349
+ return [x.strftime(self.date_format) for x in output]
399
350
 
400
351
 
401
352
  @dataclass
402
- class TimestampModifier(_DateRepresentationModifier):
353
+ class TimestampModifier(DateRepresentationModifier):
403
354
  """
404
355
  Class to modify a numeric representation of a date (as Unix/Epoch/POSIX timestamp) given a DateModifier
405
356
 
@@ -407,10 +358,6 @@ class TimestampModifier(_DateRepresentationModifier):
407
358
  date_modifier: The DateModifier to apply on datetime objects
408
359
  date_format: Format of the date string. Default is '%Y-%m-%d'
409
360
  """
410
- _date_modifiers: Sequence[DateModifier]
411
-
412
- def __init__(self, date_modifiers: Sequence[DateModifier]):
413
- super().__init__(date_modifiers)
414
361
 
415
362
  def with_more_modifiers(self, date_modifiers: Sequence[DateModifier]):
416
363
  """
@@ -422,7 +369,7 @@ class TimestampModifier(_DateRepresentationModifier):
422
369
  Returns:
423
370
  A new TimestampModifier
424
371
  """
425
- joined_modifiers = self.date_modifier.get_joined_modifiers(date_modifiers)
372
+ joined_modifiers = self.date_mod_pipeline.get_joined_modifiers(date_modifiers)
426
373
  return TimestampModifier(joined_modifiers)
427
374
 
428
375
  def modify(self, timestamp: float) -> float:
@@ -436,7 +383,7 @@ class TimestampModifier(_DateRepresentationModifier):
436
383
  The resulting timestamp
437
384
  """
438
385
  date_obj = datetime.fromtimestamp(timestamp).date()
439
- modified_date = self.date_modifier.modify(date_obj)
386
+ modified_date = self.date_mod_pipeline.modify(date_obj)
440
387
  modified_datetime = datetime.combine(modified_date, datetime.min.time())
441
388
  return modified_datetime.timestamp()
442
389
 
@@ -458,5 +405,5 @@ class TimestampModifier(_DateRepresentationModifier):
458
405
  A list of timestamp as floats
459
406
  """
460
407
  curr_date = datetime.fromtimestamp(start_timestamp).date()
461
- output = self.date_modifier.get_date_list(curr_date, step)
408
+ output = self.date_mod_pipeline.get_date_list(curr_date, step)
462
409
  return [datetime.combine(x, datetime.min.time()).timestamp() for x in output]
@@ -0,0 +1,6 @@
1
+ from ._implementation import (
2
+ DateModifier,
3
+ DayIdxOfCalendarUnit,
4
+ OffsetUnits,
5
+ DateRepresentationModifier
6
+ )