squirrels 0.4.1__py3-none-any.whl → 0.5.0rc0__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.
- squirrels/__init__.py +10 -6
- squirrels/_api_response_models.py +93 -44
- squirrels/_api_server.py +571 -219
- squirrels/_auth.py +451 -0
- squirrels/_command_line.py +61 -20
- squirrels/_connection_set.py +38 -25
- squirrels/_constants.py +44 -34
- squirrels/_dashboards_io.py +34 -16
- squirrels/_exceptions.py +57 -0
- squirrels/_initializer.py +117 -44
- squirrels/_manifest.py +124 -62
- squirrels/_model_builder.py +111 -0
- squirrels/_model_configs.py +74 -0
- squirrels/_model_queries.py +52 -0
- squirrels/_models.py +860 -354
- squirrels/_package_loader.py +8 -4
- squirrels/_parameter_configs.py +45 -65
- squirrels/_parameter_sets.py +15 -13
- squirrels/_project.py +561 -0
- squirrels/_py_module.py +4 -3
- squirrels/_seeds.py +35 -16
- squirrels/_sources.py +106 -0
- squirrels/_utils.py +166 -63
- squirrels/_version.py +1 -1
- squirrels/arguments/init_time_args.py +78 -15
- squirrels/arguments/run_time_args.py +62 -101
- squirrels/dashboards.py +4 -4
- squirrels/data_sources.py +94 -162
- squirrels/dataset_result.py +86 -0
- squirrels/dateutils.py +4 -4
- squirrels/package_data/base_project/.env +30 -0
- squirrels/package_data/base_project/.env.example +30 -0
- squirrels/package_data/base_project/.gitignore +3 -2
- squirrels/package_data/base_project/assets/expenses.db +0 -0
- squirrels/package_data/base_project/connections.yml +11 -3
- squirrels/package_data/base_project/dashboards/dashboard_example.py +15 -13
- squirrels/package_data/base_project/dashboards/dashboard_example.yml +22 -0
- squirrels/package_data/base_project/docker/.dockerignore +5 -2
- squirrels/package_data/base_project/docker/Dockerfile +3 -3
- squirrels/package_data/base_project/docker/compose.yml +1 -1
- squirrels/package_data/base_project/duckdb_init.sql +9 -0
- squirrels/package_data/base_project/macros/macros_example.sql +15 -0
- squirrels/package_data/base_project/models/builds/build_example.py +26 -0
- squirrels/package_data/base_project/models/builds/build_example.sql +16 -0
- squirrels/package_data/base_project/models/builds/build_example.yml +55 -0
- squirrels/package_data/base_project/models/dbviews/dbview_example.sql +12 -22
- squirrels/package_data/base_project/models/dbviews/dbview_example.yml +26 -0
- squirrels/package_data/base_project/models/federates/federate_example.py +38 -15
- squirrels/package_data/base_project/models/federates/federate_example.sql +16 -2
- squirrels/package_data/base_project/models/federates/federate_example.yml +65 -0
- squirrels/package_data/base_project/models/sources.yml +39 -0
- squirrels/package_data/base_project/parameters.yml +36 -21
- squirrels/package_data/base_project/pyconfigs/connections.py +6 -11
- squirrels/package_data/base_project/pyconfigs/context.py +20 -33
- squirrels/package_data/base_project/pyconfigs/parameters.py +19 -21
- squirrels/package_data/base_project/pyconfigs/user.py +23 -0
- squirrels/package_data/base_project/seeds/seed_categories.yml +15 -0
- squirrels/package_data/base_project/seeds/seed_subcategories.csv +15 -15
- squirrels/package_data/base_project/seeds/seed_subcategories.yml +21 -0
- squirrels/package_data/base_project/squirrels.yml.j2 +17 -40
- squirrels/parameters.py +20 -20
- {squirrels-0.4.1.dist-info → squirrels-0.5.0rc0.dist-info}/METADATA +31 -32
- squirrels-0.5.0rc0.dist-info/RECORD +70 -0
- {squirrels-0.4.1.dist-info → squirrels-0.5.0rc0.dist-info}/WHEEL +1 -1
- squirrels-0.5.0rc0.dist-info/entry_points.txt +3 -0
- {squirrels-0.4.1.dist-info → squirrels-0.5.0rc0.dist-info/licenses}/LICENSE +1 -1
- squirrels/_authenticator.py +0 -85
- squirrels/_environcfg.py +0 -84
- squirrels/package_data/assets/favicon.ico +0 -0
- squirrels/package_data/assets/index.css +0 -1
- squirrels/package_data/assets/index.js +0 -58
- squirrels/package_data/base_project/dashboards.yml +0 -10
- squirrels/package_data/base_project/env.yml +0 -29
- squirrels/package_data/base_project/models/dbviews/dbview_example.py +0 -47
- squirrels/package_data/base_project/pyconfigs/auth.py +0 -45
- squirrels/package_data/templates/index.html +0 -18
- squirrels/project.py +0 -378
- squirrels/user_base.py +0 -55
- squirrels-0.4.1.dist-info/RECORD +0 -60
- squirrels-0.4.1.dist-info/entry_points.txt +0 -4
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
## Note: You can copy this file to the .squirrels folder in your home directory to make
|
|
2
|
-
## the configurations global for all squirrels projects on the current machine
|
|
3
|
-
|
|
4
|
-
## Fake users for local development testing. Must have an 'auth.py' file with a 'User' model to use custom attributes like 'role'
|
|
5
|
-
users:
|
|
6
|
-
alice:
|
|
7
|
-
is_internal: True
|
|
8
|
-
password: I<3Squirrels
|
|
9
|
-
full_name: Alice Doe
|
|
10
|
-
role: manager
|
|
11
|
-
bob:
|
|
12
|
-
is_internal: False
|
|
13
|
-
password: abcd5678
|
|
14
|
-
full_name: Bob Doe
|
|
15
|
-
role: customer
|
|
16
|
-
|
|
17
|
-
## Custom environment variables / secrets
|
|
18
|
-
env_vars:
|
|
19
|
-
sqlite_conn_str: sqlite://{username}:{password}@/{project_path}/assets/expenses.db
|
|
20
|
-
|
|
21
|
-
## Database credentials
|
|
22
|
-
credentials:
|
|
23
|
-
db_user:
|
|
24
|
-
username: user1
|
|
25
|
-
password: pass1
|
|
26
|
-
|
|
27
|
-
## Predefined secrets used by the squirrels framework
|
|
28
|
-
secrets:
|
|
29
|
-
jwt_secret: ## generate a random 32 byte hex string here for the jwt secret/private key. For instance, you can run "openssl rand -hex 32" in bash
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
from textwrap import dedent
|
|
2
|
-
from squirrels import ModelArgs
|
|
3
|
-
import pandas as pd
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
def main(sqrl: ModelArgs) -> pd.DataFrame:
|
|
7
|
-
"""
|
|
8
|
-
Create a database view model in Python by sending an external query to a database or API, and return a
|
|
9
|
-
pandas DataFrame of the result in this function. Since the result is loaded into server memory, be mindful of
|
|
10
|
-
the size of the results coming from the external query.
|
|
11
|
-
"""
|
|
12
|
-
|
|
13
|
-
## If working with sqlalchemy ORMs, use 'sqrl.connections' to get a sqlalchemy engine
|
|
14
|
-
# from typing import Union
|
|
15
|
-
# engine1 = sqrl.connections[sqrl.connection_name] ## using the pre-assigned key
|
|
16
|
-
# engine2 = sqrl.connections["my_connection_name"] ## or use any defined key
|
|
17
|
-
|
|
18
|
-
## Example with building and running a sql query
|
|
19
|
-
masked_id = "id" if getattr(sqrl.user, "role", "") == "manager" else "'***'"
|
|
20
|
-
desc_cond = "description LIKE :desc_pattern" if sqrl.is_placeholder("desc_pattern") else "true"
|
|
21
|
-
category_cond = f"category IN ({sqrl.ctx['categories']})" if sqrl.ctx["has_categories"] else "true"
|
|
22
|
-
subcategory_cond = f"subcategory IN ({sqrl.ctx['subcategories']})" if sqrl.ctx["has_subcategories"] else "true"
|
|
23
|
-
query = dedent(f"""
|
|
24
|
-
WITH
|
|
25
|
-
transactions_with_masked_id AS (
|
|
26
|
-
SELECT *,
|
|
27
|
-
{masked_id} as masked_id,
|
|
28
|
-
STRFTIME('%Y-%m', date) AS month
|
|
29
|
-
FROM transactions
|
|
30
|
-
)
|
|
31
|
-
SELECT {sqrl.ctx["select_dim_cols"]}
|
|
32
|
-
, sum(-amount) as total_amount
|
|
33
|
-
FROM transactions_with_masked_id
|
|
34
|
-
WHERE date >= :start_date
|
|
35
|
-
AND date <= :end_date
|
|
36
|
-
AND -amount >= :min_amount
|
|
37
|
-
AND -amount <= :max_amount
|
|
38
|
-
AND {desc_cond}
|
|
39
|
-
AND {category_cond}
|
|
40
|
-
AND {subcategory_cond}
|
|
41
|
-
GROUP BY {sqrl.ctx["group_by_cols"]}
|
|
42
|
-
""").strip()
|
|
43
|
-
|
|
44
|
-
return sqrl.run_external_sql(query)
|
|
45
|
-
|
|
46
|
-
## A 'connection_name' argument is available to use a different connection key
|
|
47
|
-
# return sqrl.run_external_sql(query, connection_name="different_key")
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
from typing import Union
|
|
2
|
-
from squirrels import User as UserBase, AuthArgs, WrongPassword
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
class User(UserBase):
|
|
6
|
-
def set_attributes(self, **kwargs) -> None:
|
|
7
|
-
"""
|
|
8
|
-
Use this method to add custom attributes in the User model that don't exist in UserBase
|
|
9
|
-
(i.e., anything that's not 'username' or 'is_internal')
|
|
10
|
-
"""
|
|
11
|
-
self.role = kwargs["role"]
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
def get_user_if_valid(sqrl: AuthArgs) -> Union[User, WrongPassword, None]:
|
|
15
|
-
"""
|
|
16
|
-
This function allows the squirrels framework to know how to authenticate input username and password.
|
|
17
|
-
|
|
18
|
-
Return:
|
|
19
|
-
- User instance - if username and password are correct
|
|
20
|
-
- WrongPassword() - if username exists but password is incorrect
|
|
21
|
-
- None - if the username doesn't exist (and search for username will continue for "fake users" configured in env.yml)
|
|
22
|
-
"""
|
|
23
|
-
mock_users_db = {
|
|
24
|
-
"johndoe": {
|
|
25
|
-
"username": "johndoe",
|
|
26
|
-
"is_admin": True,
|
|
27
|
-
"role": "manager",
|
|
28
|
-
"hashed_password": str(hash("I<3Squirrels"))
|
|
29
|
-
},
|
|
30
|
-
"mattdoe": {
|
|
31
|
-
"username": "mattdoe",
|
|
32
|
-
"is_admin": False,
|
|
33
|
-
"role": "customer",
|
|
34
|
-
"hashed_password": str(hash("abcd5678"))
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
user_obj = mock_users_db.get(sqrl.username)
|
|
39
|
-
if user_obj is None:
|
|
40
|
-
return None
|
|
41
|
-
|
|
42
|
-
if str(hash(sqrl.password)) == user_obj["hashed_password"]:
|
|
43
|
-
return User.Create(sqrl.username, is_internal=user_obj["is_admin"], role=user_obj["role"])
|
|
44
|
-
else:
|
|
45
|
-
return WrongPassword()
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
<!doctype html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="UTF-8" />
|
|
5
|
-
<link id="favicon" rel="icon" type="image/x-icon" href="/assets/favicon.ico" />
|
|
6
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
|
-
<script>
|
|
8
|
-
const hostname = '';
|
|
9
|
-
const projectMetadataURL = '{{ project_metadata_path }}';
|
|
10
|
-
</script>
|
|
11
|
-
<title>Squirrels Testing UI</title>
|
|
12
|
-
<script type="module" crossorigin src="/assets/index.js?version=0.4.1"></script>
|
|
13
|
-
<link rel="stylesheet" crossorigin href="/assets/index.css?version=0.4.1">
|
|
14
|
-
</head>
|
|
15
|
-
<body>
|
|
16
|
-
<div id="root"></div>
|
|
17
|
-
</body>
|
|
18
|
-
</html>
|
squirrels/project.py
DELETED
|
@@ -1,378 +0,0 @@
|
|
|
1
|
-
import typing as _t, functools as _ft, asyncio as _aio, os as _os, shutil as _shutil, json as _json
|
|
2
|
-
import logging as _l, uuid as _uu, matplotlib.pyplot as _plt, networkx as _nx, pandas as _pd
|
|
3
|
-
|
|
4
|
-
from . import _utils as _u, _constants as _c, _environcfg as _ec, _manifest as _mf, _authenticator as _auth
|
|
5
|
-
from . import _seeds as _s, _connection_set as _cs, _models as _m, _dashboards_io as _d, _parameter_sets as _ps
|
|
6
|
-
from . import dashboards as _dash
|
|
7
|
-
|
|
8
|
-
T = _t.TypeVar('T', bound=_dash.Dashboard)
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class _CustomJsonFormatter(_l.Formatter):
|
|
12
|
-
def format(self, record: _l.LogRecord) -> str:
|
|
13
|
-
super().format(record)
|
|
14
|
-
info = {
|
|
15
|
-
"timestamp": self.formatTime(record),
|
|
16
|
-
"project_id": record.name,
|
|
17
|
-
"level": record.levelname,
|
|
18
|
-
"message": record.getMessage(),
|
|
19
|
-
"thread": record.thread,
|
|
20
|
-
"thread_name": record.threadName,
|
|
21
|
-
"process": record.process,
|
|
22
|
-
**record.__dict__.get("info", {})
|
|
23
|
-
}
|
|
24
|
-
output = {
|
|
25
|
-
"data": record.__dict__.get("data", {}),
|
|
26
|
-
"info": info
|
|
27
|
-
}
|
|
28
|
-
return _json.dumps(output)
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
class SquirrelsProject:
|
|
32
|
-
"""
|
|
33
|
-
Initiate an instance of this class to interact with a Squirrels project through Python code. For example this can be handy to experiment with the datasets produced by Squirrels in a Jupyter notebook.
|
|
34
|
-
"""
|
|
35
|
-
|
|
36
|
-
def __init__(self, *, filepath: str = ".", log_file: str | None = _c.LOGS_FILE, log_level: str = "INFO", log_format: str = "text") -> None:
|
|
37
|
-
"""
|
|
38
|
-
Constructor for SquirrelsProject class. Loads the file contents of the Squirrels project into memory as member fields.
|
|
39
|
-
|
|
40
|
-
Arguments:
|
|
41
|
-
filepath: The path to the Squirrels project file. Defaults to the current working directory.
|
|
42
|
-
log_level: The logging level to use. Options are "DEBUG", "INFO", and "WARNING". Default is "INFO".
|
|
43
|
-
log_file: The name of the log file to write to from the "logs/" subfolder. If None or empty string, then file logging is disabled. Default is "squirrels.log".
|
|
44
|
-
log_format: The format of the log records. Options are "text" and "json". Default is "text".
|
|
45
|
-
"""
|
|
46
|
-
self._filepath = filepath
|
|
47
|
-
self._logger = self._get_logger(self._filepath, log_file, log_level, log_format)
|
|
48
|
-
|
|
49
|
-
def _get_logger(self, base_path: str, log_file: str | None, log_level: str, log_format: str) -> _u.Logger:
|
|
50
|
-
logger = _u.Logger(name=_uu.uuid4().hex)
|
|
51
|
-
logger.setLevel(log_level.upper())
|
|
52
|
-
|
|
53
|
-
if log_file:
|
|
54
|
-
path = _u.Path(base_path, _c.LOGS_FOLDER, log_file)
|
|
55
|
-
path.parent.mkdir(parents=True, exist_ok=True)
|
|
56
|
-
|
|
57
|
-
handler = _l.FileHandler(path)
|
|
58
|
-
if log_format.lower() == "json":
|
|
59
|
-
handler.setFormatter(_CustomJsonFormatter())
|
|
60
|
-
elif log_format.lower() == "text":
|
|
61
|
-
formatter = _l.Formatter("[%(name)s] %(asctime)s - %(levelname)s - %(message)s")
|
|
62
|
-
handler.setFormatter(formatter)
|
|
63
|
-
else:
|
|
64
|
-
raise ValueError("log_format must be either 'text' or 'json'")
|
|
65
|
-
logger.addHandler(handler)
|
|
66
|
-
else:
|
|
67
|
-
logger.disabled = True
|
|
68
|
-
|
|
69
|
-
return logger
|
|
70
|
-
|
|
71
|
-
@property
|
|
72
|
-
@_ft.cache
|
|
73
|
-
def _env_cfg(self) -> _ec.EnvironConfig:
|
|
74
|
-
return _ec.EnvironConfigIO.load_from_file(self._logger, self._filepath)
|
|
75
|
-
|
|
76
|
-
@property
|
|
77
|
-
@_ft.cache
|
|
78
|
-
def _manifest_cfg(self) -> _mf.ManifestConfig:
|
|
79
|
-
return _mf.ManifestIO.load_from_file(self._logger, self._filepath, self._env_cfg)
|
|
80
|
-
|
|
81
|
-
@property
|
|
82
|
-
@_ft.cache
|
|
83
|
-
def _seeds(self) -> _s.Seeds:
|
|
84
|
-
return _s.SeedsIO.load_files(self._logger, self._filepath, self._manifest_cfg)
|
|
85
|
-
|
|
86
|
-
@property
|
|
87
|
-
@_ft.cache
|
|
88
|
-
def _model_files(self) -> dict[str, _m.QueryFile]:
|
|
89
|
-
return _m.ModelsIO.load_files(self._logger, self._filepath)
|
|
90
|
-
|
|
91
|
-
@property
|
|
92
|
-
@_ft.cache
|
|
93
|
-
def _context_func(self) -> _m.ContextFunc:
|
|
94
|
-
return _m.ModelsIO.load_context_func(self._logger, self._filepath)
|
|
95
|
-
|
|
96
|
-
@property
|
|
97
|
-
@_ft.cache
|
|
98
|
-
def _dashboards(self) -> dict[str, _d.DashboardFunction]:
|
|
99
|
-
return _d.DashboardsIO.load_files(self._logger, self._filepath)
|
|
100
|
-
|
|
101
|
-
@property
|
|
102
|
-
@_ft.cache
|
|
103
|
-
def _conn_args(self) -> _cs.ConnectionsArgs:
|
|
104
|
-
return _cs.ConnectionSetIO.load_conn_py_args(self._logger, self._env_cfg, self._manifest_cfg)
|
|
105
|
-
|
|
106
|
-
@property
|
|
107
|
-
def _conn_set(self) -> _cs.ConnectionSet:
|
|
108
|
-
if not hasattr(self, "__conn_set") or self.__conn_set is None:
|
|
109
|
-
self.__conn_set = _cs.ConnectionSetIO.load_from_file(self._logger, self._filepath, self._manifest_cfg, self._conn_args)
|
|
110
|
-
return self.__conn_set
|
|
111
|
-
|
|
112
|
-
@property
|
|
113
|
-
@_ft.cache
|
|
114
|
-
def _authenticator(self) -> _auth.Authenticator:
|
|
115
|
-
token_expiry_minutes = self._manifest_cfg.settings.get(_c.AUTH_TOKEN_EXPIRE_SETTING, 30)
|
|
116
|
-
return _auth.Authenticator(self._filepath, self._env_cfg, self._conn_args, self._conn_set, token_expiry_minutes)
|
|
117
|
-
|
|
118
|
-
@property
|
|
119
|
-
@_ft.cache
|
|
120
|
-
def _param_args(self) -> _ps.ParametersArgs:
|
|
121
|
-
return _ps.ParameterConfigsSetIO.get_param_args(self._conn_args)
|
|
122
|
-
|
|
123
|
-
@property
|
|
124
|
-
@_ft.cache
|
|
125
|
-
def _param_cfg_set(self) -> _ps.ParameterConfigsSet:
|
|
126
|
-
return _ps.ParameterConfigsSetIO.load_from_file(
|
|
127
|
-
self._logger, self._filepath, self._manifest_cfg, self._seeds, self._conn_set, self._param_args
|
|
128
|
-
)
|
|
129
|
-
|
|
130
|
-
@property
|
|
131
|
-
@_ft.cache
|
|
132
|
-
def _j2_env(self) -> _u.EnvironmentWithMacros:
|
|
133
|
-
return _u.EnvironmentWithMacros(self._logger, loader=_u.j2.FileSystemLoader(self._filepath))
|
|
134
|
-
|
|
135
|
-
@property
|
|
136
|
-
@_ft.cache
|
|
137
|
-
def User(self) -> type[_auth.User]:
|
|
138
|
-
"""
|
|
139
|
-
A direct reference to the User class in the `auth.py` file (if applicable). If `auth.py` does not exist, then this returns the `squirrels.User` class.
|
|
140
|
-
"""
|
|
141
|
-
return self._authenticator.user_cls
|
|
142
|
-
|
|
143
|
-
def close(self) -> None:
|
|
144
|
-
"""
|
|
145
|
-
Deliberately close any open resources within the Squirrels project, such as database connections (instead of relying on the garbage collector).
|
|
146
|
-
"""
|
|
147
|
-
if hasattr(self, "__conn_set") and self.__conn_set is not None:
|
|
148
|
-
self.__conn_set.dispose()
|
|
149
|
-
self.__conn_set = None
|
|
150
|
-
|
|
151
|
-
def __exit__(self, exc_type, exc_val, traceback):
|
|
152
|
-
self.close()
|
|
153
|
-
|
|
154
|
-
def _generate_dag(self, dataset: str, *, target_model_name: str | None = None, always_pandas: bool = False) -> _m.DAG:
|
|
155
|
-
seeds_dict = self._seeds.get_dataframes()
|
|
156
|
-
|
|
157
|
-
models_dict: dict[str, _m.Referable] = {key: _m.Seed(key, df) for key, df in seeds_dict.items()}
|
|
158
|
-
for key, val in self._model_files.items():
|
|
159
|
-
models_dict[key] = _m.Model(key, val, self._manifest_cfg, self._conn_set, self._logger, j2_env=self._j2_env)
|
|
160
|
-
models_dict[key].needs_pandas = always_pandas
|
|
161
|
-
|
|
162
|
-
dataset_config = self._manifest_cfg.datasets[dataset]
|
|
163
|
-
target_model_name = dataset_config.model if target_model_name is None else target_model_name
|
|
164
|
-
target_model = models_dict[target_model_name]
|
|
165
|
-
target_model.is_target = True
|
|
166
|
-
|
|
167
|
-
return _m.DAG(self._manifest_cfg, dataset_config, target_model, models_dict, self._logger)
|
|
168
|
-
|
|
169
|
-
def _draw_dag(self, dag: _m.DAG, output_folder: _u.Path) -> None:
|
|
170
|
-
color_map = {_m.ModelType.SEED: "green", _m.ModelType.DBVIEW: "red", _m.ModelType.FEDERATE: "skyblue"}
|
|
171
|
-
|
|
172
|
-
G = dag.to_networkx_graph()
|
|
173
|
-
|
|
174
|
-
fig, _ = _plt.subplots()
|
|
175
|
-
pos = _nx.multipartite_layout(G, subset_key="layer")
|
|
176
|
-
colors = [color_map[node[1]] for node in G.nodes(data="model_type")] # type: ignore
|
|
177
|
-
_nx.draw(G, pos=pos, node_shape='^', node_size=1000, node_color=colors, arrowsize=20)
|
|
178
|
-
|
|
179
|
-
y_values = [val[1] for val in pos.values()]
|
|
180
|
-
scale = max(y_values) - min(y_values) if len(y_values) > 0 else 0
|
|
181
|
-
label_pos = {key: (val[0], val[1]-0.002-0.1*scale) for key, val in pos.items()}
|
|
182
|
-
_nx.draw_networkx_labels(G, pos=label_pos, font_size=8)
|
|
183
|
-
|
|
184
|
-
fig.tight_layout()
|
|
185
|
-
_plt.margins(x=0.1, y=0.1)
|
|
186
|
-
fig.savefig(_u.Path(output_folder, "dag.png"))
|
|
187
|
-
_plt.close(fig)
|
|
188
|
-
|
|
189
|
-
async def _write_dataset_outputs_given_test_set(
|
|
190
|
-
self, dataset: str, select: str, test_set: str | None, runquery: bool, recurse: bool
|
|
191
|
-
) -> _t.Any | None:
|
|
192
|
-
dataset_conf = self._manifest_cfg.datasets[dataset]
|
|
193
|
-
default_test_set_conf = self._manifest_cfg.get_default_test_set(dataset)
|
|
194
|
-
if test_set in self._manifest_cfg.selection_test_sets:
|
|
195
|
-
test_set_conf = self._manifest_cfg.selection_test_sets[test_set]
|
|
196
|
-
elif test_set is None or test_set == default_test_set_conf.name:
|
|
197
|
-
test_set, test_set_conf = default_test_set_conf.name, default_test_set_conf
|
|
198
|
-
else:
|
|
199
|
-
raise _u.ConfigurationError(f"No test set named '{test_set}' was found when compiling dataset '{dataset}'. The test set must be defined if not default for dataset.")
|
|
200
|
-
|
|
201
|
-
error_msg_intro = f"Cannot compile dataset '{dataset}' with test set '{test_set}'."
|
|
202
|
-
if test_set_conf.datasets is not None and dataset not in test_set_conf.datasets:
|
|
203
|
-
raise _u.ConfigurationError(f"{error_msg_intro}\n Applicable datasets for test set '{test_set}' does not include dataset '{dataset}'.")
|
|
204
|
-
|
|
205
|
-
user_attributes = test_set_conf.user_attributes.copy()
|
|
206
|
-
selections = test_set_conf.parameters.copy()
|
|
207
|
-
username, is_internal = user_attributes.pop("username", ""), user_attributes.pop("is_internal", False)
|
|
208
|
-
if test_set_conf.is_authenticated:
|
|
209
|
-
user = self.User.Create(username, is_internal=is_internal, **user_attributes)
|
|
210
|
-
elif dataset_conf.scope == _mf.DatasetScope.PUBLIC:
|
|
211
|
-
user = None
|
|
212
|
-
else:
|
|
213
|
-
raise _u.ConfigurationError(f"{error_msg_intro}\n Non-public datasets require a test set with 'user_attributes' section defined")
|
|
214
|
-
|
|
215
|
-
if dataset_conf.scope == _mf.DatasetScope.PRIVATE and not is_internal:
|
|
216
|
-
raise _u.ConfigurationError(f"{error_msg_intro}\n Private datasets require a test set with user_attribute 'is_internal' set to true")
|
|
217
|
-
|
|
218
|
-
# always_pandas is set to True for creating CSV files from results (when runquery is True)
|
|
219
|
-
dag = self._generate_dag(dataset, target_model_name=select, always_pandas=True)
|
|
220
|
-
placeholders = await dag.execute(self._param_args, self._param_cfg_set, self._context_func, user, selections, runquery=runquery, recurse=recurse)
|
|
221
|
-
|
|
222
|
-
output_folder = _u.Path(self._filepath, _c.TARGET_FOLDER, _c.COMPILE_FOLDER, dataset, test_set)
|
|
223
|
-
if _os.path.exists(output_folder):
|
|
224
|
-
_shutil.rmtree(output_folder)
|
|
225
|
-
_os.makedirs(output_folder, exist_ok=True)
|
|
226
|
-
|
|
227
|
-
def write_placeholders() -> None:
|
|
228
|
-
output_filepath = _u.Path(output_folder, "placeholders.json")
|
|
229
|
-
with open(output_filepath, 'w') as f:
|
|
230
|
-
_json.dump(placeholders, f, indent=4)
|
|
231
|
-
|
|
232
|
-
def write_model_outputs(model: _m.Referable) -> None:
|
|
233
|
-
assert isinstance(model, _m.Model)
|
|
234
|
-
subfolder = _c.DBVIEWS_FOLDER if model.query_file.model_type == _m.ModelType.DBVIEW else _c.FEDERATES_FOLDER
|
|
235
|
-
subpath = _u.Path(output_folder, subfolder)
|
|
236
|
-
_os.makedirs(subpath, exist_ok=True)
|
|
237
|
-
if isinstance(model.compiled_query, _m.SqlModelQuery):
|
|
238
|
-
output_filepath = _u.Path(subpath, model.name+'.sql')
|
|
239
|
-
query = model.compiled_query.query
|
|
240
|
-
with open(output_filepath, 'w') as f:
|
|
241
|
-
f.write(query)
|
|
242
|
-
if runquery and isinstance(model.result, _pd.DataFrame):
|
|
243
|
-
output_filepath = _u.Path(subpath, model.name+'.csv')
|
|
244
|
-
model.result.to_csv(output_filepath, index=False)
|
|
245
|
-
|
|
246
|
-
write_placeholders()
|
|
247
|
-
all_model_names = dag.get_all_query_models()
|
|
248
|
-
coroutines = [_aio.to_thread(write_model_outputs, dag.models_dict[name]) for name in all_model_names]
|
|
249
|
-
await _aio.gather(*coroutines)
|
|
250
|
-
|
|
251
|
-
if recurse:
|
|
252
|
-
self._draw_dag(dag, output_folder)
|
|
253
|
-
|
|
254
|
-
if isinstance(dag.target_model, _m.Model) and dag.target_model.compiled_query is not None:
|
|
255
|
-
return dag.target_model.compiled_query.query # else return None
|
|
256
|
-
|
|
257
|
-
async def compile(
|
|
258
|
-
self, *, dataset: str | None = None, do_all_datasets: bool = False, selected_model: str | None = None, test_set: str | None = None,
|
|
259
|
-
do_all_test_sets: bool = False, runquery: bool = False
|
|
260
|
-
) -> None:
|
|
261
|
-
"""
|
|
262
|
-
Async method to compile the SQL templates into files in the "target/" folder. Same functionality as the "sqrl compile" CLI.
|
|
263
|
-
|
|
264
|
-
Although all arguments are "optional", the "dataset" argument is required if "do_all_datasets" argument is False.
|
|
265
|
-
|
|
266
|
-
Arguments:
|
|
267
|
-
dataset: The name of the dataset to compile. Ignored if "do_all_datasets" argument is True, but required (i.e., cannot be None) if "do_all_datasets" is False. Default is None.
|
|
268
|
-
do_all_datasets: If True, compile all datasets and ignore the "dataset" argument. Default is False.
|
|
269
|
-
selected_model: The name of the model to compile. If specified, the compiled SQL query is also printed in the terminal. If None, all models for the selected dataset are compiled. Default is None.
|
|
270
|
-
test_set: The name of the test set to compile with. If None, the default test set is used (which can vary by dataset). Ignored if `do_all_test_sets` argument is True. Default is None.
|
|
271
|
-
do_all_test_sets: Whether to compile all applicable test sets for the selected dataset(s). If True, the `test_set` argument is ignored. Default is False.
|
|
272
|
-
runquery**: Whether to run all compiled queries and save each result as a CSV file. If True and `selected_model` is specified, all upstream models of the selected model is compiled as well. Default is False.
|
|
273
|
-
"""
|
|
274
|
-
recurse = True
|
|
275
|
-
if do_all_datasets:
|
|
276
|
-
selected_models = [(dataset.name, dataset.model) for dataset in self._manifest_cfg.datasets.values()]
|
|
277
|
-
else:
|
|
278
|
-
assert isinstance(dataset, str), "argument 'dataset' must be provided a string value if argument 'do_all_datasets' is False"
|
|
279
|
-
assert dataset in self._manifest_cfg.datasets, f"dataset '{dataset}' not found in {_c.MANIFEST_FILE}"
|
|
280
|
-
if selected_model is None:
|
|
281
|
-
selected_model = self._manifest_cfg.datasets[dataset].model
|
|
282
|
-
else:
|
|
283
|
-
recurse = False
|
|
284
|
-
selected_models = [(dataset, selected_model)]
|
|
285
|
-
|
|
286
|
-
coroutines: list[_t.Coroutine] = []
|
|
287
|
-
for dataset, selected_model in selected_models:
|
|
288
|
-
if do_all_test_sets:
|
|
289
|
-
for test_set_name in self._manifest_cfg.get_applicable_test_sets(dataset):
|
|
290
|
-
coroutine = self._write_dataset_outputs_given_test_set(dataset, selected_model, test_set_name, runquery, recurse)
|
|
291
|
-
coroutines.append(coroutine)
|
|
292
|
-
|
|
293
|
-
coroutine = self._write_dataset_outputs_given_test_set(dataset, selected_model, test_set, runquery, recurse)
|
|
294
|
-
coroutines.append(coroutine)
|
|
295
|
-
|
|
296
|
-
queries = await _aio.gather(*coroutines)
|
|
297
|
-
|
|
298
|
-
print(f"Compiled successfully! See the '{_c.TARGET_FOLDER}/' folder for results.")
|
|
299
|
-
print()
|
|
300
|
-
if not recurse and len(queries) == 1 and isinstance(queries[0], str):
|
|
301
|
-
print(queries[0])
|
|
302
|
-
print()
|
|
303
|
-
|
|
304
|
-
def _permission_error(self, user: _auth.User | None, data_type: str, data_name: str, scope: str) -> PermissionError:
|
|
305
|
-
username = None if user is None else user.username
|
|
306
|
-
return PermissionError(f"User '{username}' does not have permission to access {scope} {data_type}: {data_name}")
|
|
307
|
-
|
|
308
|
-
def seed(self, name: str) -> _pd.DataFrame:
|
|
309
|
-
"""
|
|
310
|
-
Method to retrieve a seed as a pandas DataFrame given a seed name.
|
|
311
|
-
|
|
312
|
-
Arguments:
|
|
313
|
-
name: The name of the seed to retrieve
|
|
314
|
-
|
|
315
|
-
Returns:
|
|
316
|
-
The seed as a pandas DataFrame
|
|
317
|
-
"""
|
|
318
|
-
seeds_dict = self._seeds.get_dataframes()
|
|
319
|
-
try:
|
|
320
|
-
return seeds_dict[name]
|
|
321
|
-
except KeyError:
|
|
322
|
-
available_seeds = list(seeds_dict.keys())
|
|
323
|
-
raise KeyError(f"Seed '{name}' not found. Available seeds are: {available_seeds}")
|
|
324
|
-
|
|
325
|
-
async def _dataset_helper(
|
|
326
|
-
self, name: str, selections: dict[str, _t.Any], user: _auth.User | None
|
|
327
|
-
) -> _pd.DataFrame:
|
|
328
|
-
dag = self._generate_dag(name)
|
|
329
|
-
await dag.execute(self._param_args, self._param_cfg_set, self._context_func, user, dict(selections))
|
|
330
|
-
return _pd.DataFrame(dag.target_model.result)
|
|
331
|
-
|
|
332
|
-
async def dataset(
|
|
333
|
-
self, name: str, *, selections: dict[str, _t.Any] = {}, user: _auth.User | None = None
|
|
334
|
-
) -> _pd.DataFrame:
|
|
335
|
-
"""
|
|
336
|
-
Async method to retrieve a dataset as a pandas DataFrame given parameter selections.
|
|
337
|
-
|
|
338
|
-
Arguments:
|
|
339
|
-
name: The name of the dataset to retrieve.
|
|
340
|
-
selections: A dictionary of parameter selections to apply to the dataset. Optional, default is empty dictionary.
|
|
341
|
-
user: The user to use for authentication. If None, no user is used. Optional, default is None.
|
|
342
|
-
|
|
343
|
-
Returns:
|
|
344
|
-
A pandas DataFrame containing the dataset.
|
|
345
|
-
"""
|
|
346
|
-
scope = self._manifest_cfg.datasets[name].scope
|
|
347
|
-
if not self._authenticator.can_user_access_scope(user, scope):
|
|
348
|
-
raise self._permission_error(user, "dataset", name, scope.name)
|
|
349
|
-
return await self._dataset_helper(name, selections, user)
|
|
350
|
-
|
|
351
|
-
async def dashboard(
|
|
352
|
-
self, name: str, *, selections: dict[str, _t.Any] = {}, user: _auth.User | None = None, dashboard_type: _t.Type[T] = _dash.Dashboard
|
|
353
|
-
) -> T:
|
|
354
|
-
"""
|
|
355
|
-
Async method to retrieve a dashboard given parameter selections.
|
|
356
|
-
|
|
357
|
-
Arguments:
|
|
358
|
-
name: The name of the dashboard to retrieve.
|
|
359
|
-
selections: A dictionary of parameter selections to apply to the dashboard. Optional, default is empty dictionary.
|
|
360
|
-
user: The user to use for authentication. If None, no user is used. Optional, default is None.
|
|
361
|
-
dashboard_type: Return type of the method (mainly used for type hints). For instance, provide PngDashboard if you want the return type to be a PngDashboard. Optional, default is squirrels.Dashboard.
|
|
362
|
-
|
|
363
|
-
Returns:
|
|
364
|
-
The dashboard type specified by the "dashboard_type" argument.
|
|
365
|
-
"""
|
|
366
|
-
scope = self._manifest_cfg.dashboards[name].scope
|
|
367
|
-
if not self._authenticator.can_user_access_scope(user, scope):
|
|
368
|
-
raise self._permission_error(user, "dashboard", name, scope.name)
|
|
369
|
-
|
|
370
|
-
async def get_dataset(dataset_name: str, fixed_params: dict[str, _t.Any]) -> _pd.DataFrame:
|
|
371
|
-
final_selections = {**selections, **fixed_params}
|
|
372
|
-
return await self._dataset_helper(dataset_name, final_selections, user)
|
|
373
|
-
|
|
374
|
-
args = _d.DashboardArgs(self._param_args.proj_vars, self._param_args.env_vars, get_dataset)
|
|
375
|
-
try:
|
|
376
|
-
return await self._dashboards[name].get_dashboard(args, dashboard_type=dashboard_type)
|
|
377
|
-
except KeyError:
|
|
378
|
-
raise KeyError(f"No dashboard file found for: {name}")
|
squirrels/user_base.py
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import typing as _t, dataclasses as _dc
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
@_dc.dataclass
|
|
5
|
-
class User:
|
|
6
|
-
"""
|
|
7
|
-
Base class for extending the custom User model class
|
|
8
|
-
|
|
9
|
-
Attributes:
|
|
10
|
-
username: The identifier for the user
|
|
11
|
-
is_internal: Setting this to True lets the user access "private" datasets
|
|
12
|
-
"""
|
|
13
|
-
username: str
|
|
14
|
-
is_internal: bool
|
|
15
|
-
|
|
16
|
-
def __hash__(self) -> int:
|
|
17
|
-
return hash(self.username)
|
|
18
|
-
|
|
19
|
-
def set_attributes(self, **kwargs) -> None:
|
|
20
|
-
"""
|
|
21
|
-
Can be overwritten in "auth.py" to introduce custom attributes. Does nothing by default
|
|
22
|
-
"""
|
|
23
|
-
pass
|
|
24
|
-
|
|
25
|
-
@classmethod
|
|
26
|
-
def Create(cls, username: str, *, is_internal: bool = False, **kwargs):
|
|
27
|
-
"""
|
|
28
|
-
Creates an instance of the User class and calls the `set_attributes` method on the new instance.
|
|
29
|
-
|
|
30
|
-
We may overwrite the `set_attributes` method in `auth.py`. We do not overwrite the constructor to guarantee that `username` and `is_internal` are always set.
|
|
31
|
-
|
|
32
|
-
Arguments:
|
|
33
|
-
username: The identifier for the user
|
|
34
|
-
is_internal: Setting this to True lets the user access "private" datasets. Default is False
|
|
35
|
-
"""
|
|
36
|
-
user = cls(username, is_internal)
|
|
37
|
-
user.set_attributes(**kwargs)
|
|
38
|
-
return user
|
|
39
|
-
|
|
40
|
-
@classmethod
|
|
41
|
-
def _FromDict(cls, user_obj_as_dict: dict[str, _t.Any]):
|
|
42
|
-
username, is_internal = user_obj_as_dict["username"], user_obj_as_dict["is_internal"]
|
|
43
|
-
user = cls(username=username, is_internal=is_internal)
|
|
44
|
-
for key, val in user_obj_as_dict.items():
|
|
45
|
-
setattr(user, key, val)
|
|
46
|
-
return user
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
@_dc.dataclass
|
|
50
|
-
class WrongPassword:
|
|
51
|
-
"""
|
|
52
|
-
Return this object if the username was found but the password was incorrect
|
|
53
|
-
|
|
54
|
-
This ensures that if the username exists as a real user, we won't continue to use the env.yml file to authenticate
|
|
55
|
-
"""
|
squirrels-0.4.1.dist-info/RECORD
DELETED
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
squirrels/__init__.py,sha256=EbovxLpc9QcDooaME7887ur1lSjouWS36sealX_QOoo,946
|
|
2
|
-
squirrels/_api_response_models.py,sha256=dHATCC80lJyQNRHhnCbG1uMWWOinxPfkq7YIG4GqD1I,9786
|
|
3
|
-
squirrels/_api_server.py,sha256=GLn31Ki7gSxvYIOnVCp6wzZkuDT1MjHKnvhBEsasnB0,30902
|
|
4
|
-
squirrels/_authenticator.py,sha256=21BADaTQ-ZfnTZMXTfNYiZFntTdm8QfRsh7X8tFJ3mA,4038
|
|
5
|
-
squirrels/_command_line.py,sha256=UzD3B6JJY9rBzb2WBCNsOwUkBWaSFz0zZnsaHATp7qU,8115
|
|
6
|
-
squirrels/_connection_set.py,sha256=oH5qp6PZwkLANCUEKkjKASe80V8SLK98-ZBaPtbUCpk,2867
|
|
7
|
-
squirrels/_constants.py,sha256=k0KF2VIIvzeyfR1ycAwDR8uHZYfQJSBho90GbTRnvIM,2983
|
|
8
|
-
squirrels/_dashboards_io.py,sha256=Qf2ow2hI57NRGtNB0rxtXy7XoNVxkvKuAFnVRxslLqY,2417
|
|
9
|
-
squirrels/_environcfg.py,sha256=rAgDpJ4-h4yzFEbSG6gKh2XbQO3pAULB6Vo-66QAYp8,2875
|
|
10
|
-
squirrels/_initializer.py,sha256=OzPEnb3uN_lf4o5bCA_r9I9D3XHD2-v-27d6trEjMjE,9809
|
|
11
|
-
squirrels/_manifest.py,sha256=9DpDidPbKJ5GvXeW31fcg4nL7tRu8hqLmkBRbimep24,7626
|
|
12
|
-
squirrels/_models.py,sha256=1Th1Vl_a1ddxjAGHCwq5sqx7RQsi5wYyyNUpRYvTx18,23641
|
|
13
|
-
squirrels/_package_loader.py,sha256=DugsBkmm6OP8KPcJxmtuAMFnhf7t43rCRPjXXSwHXGs,1041
|
|
14
|
-
squirrels/_parameter_configs.py,sha256=NF7ChgkAVlsTFrL5QkhEbsIbohcSFfuu4IvhxDRao0s,24731
|
|
15
|
-
squirrels/_parameter_sets.py,sha256=YwyDp-8_VuKUPZ3oS9JWC9ynFlWy2sbiRDSXTGDBc4c,9702
|
|
16
|
-
squirrels/_py_module.py,sha256=w4AYGrks121_osRxL9F8hiX0ZuPXC6krnJB8cOqhWBI,2564
|
|
17
|
-
squirrels/_seeds.py,sha256=gLMlDphnwQKzSWLjpQBGYNPy5XtS0xu2PReqkk55na4,1462
|
|
18
|
-
squirrels/_utils.py,sha256=ykV3hjQTxG9-blmAPxCfd78bJXR1YfH_rQdUBsTaRJM,8829
|
|
19
|
-
squirrels/_version.py,sha256=AdLe_EtrKO8nswLboAlbA_5SlyJnV7ekvhzCLvOhrVQ,105
|
|
20
|
-
squirrels/arguments/init_time_args.py,sha256=Rc6n9vVZ7p4WpbyAvryG-tGb4vRQKrRyfRdzy3w5WTc,925
|
|
21
|
-
squirrels/arguments/run_time_args.py,sha256=Dk3H-iPlsy4i3swAVs8Uk6yyQPko7CEb9acfqHnzcvc,7031
|
|
22
|
-
squirrels/dashboards.py,sha256=NeLcBxfIOJV-hlBllQ4mB4sSHFxAF-odjyiioUF19z4,1978
|
|
23
|
-
squirrels/data_sources.py,sha256=qPtH5JFtCd8axjrXa40UlsZHU3geWTzxIkw3UAjj8lA,30019
|
|
24
|
-
squirrels/dateutils.py,sha256=dvbXf9-VEt8PhDnY3rd6kjqFgVkWPOhsfsq8cq9kHdE,16734
|
|
25
|
-
squirrels/package_data/assets/favicon.ico,sha256=FZx26dn50cp0rgYdyBptJJob2TTVNiY0NZ-MeeL_uY0,61022
|
|
26
|
-
squirrels/package_data/assets/index.css,sha256=BJVYY2dse9-vhASMsls6j5gpf33Z9tCzR1VyX1rWkH0,12181
|
|
27
|
-
squirrels/package_data/assets/index.js,sha256=hrD3gmRFTBm2OUU1_mE-qBjoOEo28Jc6A6p-igHl95M,265535
|
|
28
|
-
squirrels/package_data/base_project/.gitignore,sha256=x7VR530sbdKJU83mF9OGD8b-nI92MbWqUnMPyB0Xz4U,137
|
|
29
|
-
squirrels/package_data/base_project/assets/expenses.db,sha256=47ichjBqC25th7B0G5rz_yYJ4V3uQeF3cnttekQYa68,28672
|
|
30
|
-
squirrels/package_data/base_project/assets/weather.db,sha256=dsHPO36gQdZ4ULAA726Hg3jp8a1dCdig1DhrGg8wTeg,86016
|
|
31
|
-
squirrels/package_data/base_project/connections.yml,sha256=-rR6kjWl8FyHTzf2RnPqR1JEfhpX_pBrOJUxf5grRQY,259
|
|
32
|
-
squirrels/package_data/base_project/dashboards/dashboard_example.py,sha256=pAvxMdrKT_-7cFcRH7qqVIaQs1oFXCg5PEwiwCi0xdY,1781
|
|
33
|
-
squirrels/package_data/base_project/dashboards.yml,sha256=UEa9YI8ymi3tH15357oCWF5ZynNmyG2yu8B2F5Cr6d4,158
|
|
34
|
-
squirrels/package_data/base_project/docker/.dockerignore,sha256=0BJ26C2eyVJPwbW1e4cmBg1TPADio4tqakd3hdojsq8,176
|
|
35
|
-
squirrels/package_data/base_project/docker/Dockerfile,sha256=Sz4g9liLD40ci2iG2ohTjth3kvmNaCAo2LWZl6mCGR8,474
|
|
36
|
-
squirrels/package_data/base_project/docker/compose.yml,sha256=u-P7Zh7IRfYAcebx4zE119OWn2m_TugGMh1IgPDl_2g,111
|
|
37
|
-
squirrels/package_data/base_project/env.yml,sha256=5BUtjX8JEkVbtp7Xj-V-KcBkBJ6GEV1n05vasDSEYxw,943
|
|
38
|
-
squirrels/package_data/base_project/models/dbviews/dbview_example.py,sha256=Kx32kJFqmwJSu519nqls0cDuN8h_YnuyxAcD_eLeNWk,2036
|
|
39
|
-
squirrels/package_data/base_project/models/dbviews/dbview_example.sql,sha256=BJGTU25fdwCDXSEMFvXjn5Lnfi_Gu0wY2nir-d5VvUo,747
|
|
40
|
-
squirrels/package_data/base_project/models/federates/federate_example.py,sha256=rLPA8UTpQN_vbCQcID22QWfI1Mu7s_Xly76medWq7DQ,712
|
|
41
|
-
squirrels/package_data/base_project/models/federates/federate_example.sql,sha256=bHK1k8Q0uc2tZI21cw1y-w1w6Ah9dKRHRDwWVd80_Tk,75
|
|
42
|
-
squirrels/package_data/base_project/parameters.yml,sha256=UUZoip-_X9axROZ9v_j-CkrUS-0x5lI0Kq02cRAkym8,6397
|
|
43
|
-
squirrels/package_data/base_project/pyconfigs/auth.py,sha256=GyV84-lSfn0uGSP_kjvEFIHJCs3II3m97cYBYYYoQSc,1553
|
|
44
|
-
squirrels/package_data/base_project/pyconfigs/connections.py,sha256=yb46igO8qH64tYczf0SJDtqyqxq5CANutzfKMtNJ96k,753
|
|
45
|
-
squirrels/package_data/base_project/pyconfigs/context.py,sha256=tXQvdiHtESiHJup3yAkaKIuAEgFnV8H-Bz35VlqxIE0,3977
|
|
46
|
-
squirrels/package_data/base_project/pyconfigs/parameters.py,sha256=5liqj98vLUus0kQGo4ae29ncr6zUpopNTXPhcnE3E5M,4940
|
|
47
|
-
squirrels/package_data/base_project/seeds/seed_categories.csv,sha256=jppjf1nOIxy7-bi5lJn5CVqmnLfJHHq0ABgp6UqbXnw,104
|
|
48
|
-
squirrels/package_data/base_project/seeds/seed_subcategories.csv,sha256=aZkBJ6KioyYjEwRunYiA8ec0X1ygiEmLRVicJecFzfY,327
|
|
49
|
-
squirrels/package_data/base_project/squirrels.yml.j2,sha256=OhYjBjDLRtCWhSiAssslGrX5MsHQO8jC6HKz_NFnR_w,3076
|
|
50
|
-
squirrels/package_data/base_project/tmp/.gitignore,sha256=XImoqcWvJY0C0L_TWCx1ljvqU7qh9fUTJmK4ACCmNFI,13
|
|
51
|
-
squirrels/package_data/templates/index.html,sha256=qJ_bLISRtJfziyRNpCe6dHUJ2UBvsrV6lsveEHeJuK0,617
|
|
52
|
-
squirrels/parameter_options.py,sha256=cWYKNoBUopHq6VfaeBu-nN2V0_IY3OgYpmYhKODNCew,16956
|
|
53
|
-
squirrels/parameters.py,sha256=aSciLRxjY_-07VAv9mGxFQnhtjnuk4XT43MhZwVfoFY,56018
|
|
54
|
-
squirrels/project.py,sha256=LxQbSOjEWdq5ZCj2u-03qMX1WJqKBk1_GPb3nCmsU0U,19256
|
|
55
|
-
squirrels/user_base.py,sha256=gFtdi9K6RJovfKqRU-b-L1bOSdgNYQCQgfez3hz9EOo,1852
|
|
56
|
-
squirrels-0.4.1.dist-info/LICENSE,sha256=CItkBKs5m4J5jhkjXoW7IAnFyOC86x4hyOATpczUMmM,11338
|
|
57
|
-
squirrels-0.4.1.dist-info/METADATA,sha256=-Vbp7skC7iXME6qBLRe4-rJCI-KuMzYGiVYpLl-ayi4,5217
|
|
58
|
-
squirrels-0.4.1.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
|
59
|
-
squirrels-0.4.1.dist-info/entry_points.txt,sha256=mYQRuGxbg8X82hjNRJuWiON4S6kE5CPvmXmxNPtYTbg,92
|
|
60
|
-
squirrels-0.4.1.dist-info/RECORD,,
|