kiln-ai 0.0.4__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 kiln-ai might be problematic. Click here for more details.

Files changed (33) hide show
  1. kiln_ai/adapters/base_adapter.py +168 -0
  2. kiln_ai/adapters/langchain_adapters.py +113 -0
  3. kiln_ai/adapters/ml_model_list.py +436 -0
  4. kiln_ai/adapters/prompt_builders.py +122 -0
  5. kiln_ai/adapters/repair/repair_task.py +71 -0
  6. kiln_ai/adapters/repair/test_repair_task.py +248 -0
  7. kiln_ai/adapters/test_langchain_adapter.py +50 -0
  8. kiln_ai/adapters/test_ml_model_list.py +99 -0
  9. kiln_ai/adapters/test_prompt_adaptors.py +167 -0
  10. kiln_ai/adapters/test_prompt_builders.py +315 -0
  11. kiln_ai/adapters/test_saving_adapter_results.py +168 -0
  12. kiln_ai/adapters/test_structured_output.py +218 -0
  13. kiln_ai/datamodel/__init__.py +362 -2
  14. kiln_ai/datamodel/basemodel.py +372 -0
  15. kiln_ai/datamodel/json_schema.py +45 -0
  16. kiln_ai/datamodel/test_basemodel.py +277 -0
  17. kiln_ai/datamodel/test_datasource.py +107 -0
  18. kiln_ai/datamodel/test_example_models.py +644 -0
  19. kiln_ai/datamodel/test_json_schema.py +124 -0
  20. kiln_ai/datamodel/test_models.py +190 -0
  21. kiln_ai/datamodel/test_nested_save.py +205 -0
  22. kiln_ai/datamodel/test_output_rating.py +88 -0
  23. kiln_ai/utils/config.py +170 -0
  24. kiln_ai/utils/formatting.py +5 -0
  25. kiln_ai/utils/test_config.py +245 -0
  26. {kiln_ai-0.0.4.dist-info → kiln_ai-0.5.0.dist-info}/METADATA +20 -1
  27. kiln_ai-0.5.0.dist-info/RECORD +29 -0
  28. kiln_ai/__init.__.py +0 -3
  29. kiln_ai/coreadd.py +0 -3
  30. kiln_ai/datamodel/project.py +0 -15
  31. kiln_ai-0.0.4.dist-info/RECORD +0 -8
  32. {kiln_ai-0.0.4.dist-info → kiln_ai-0.5.0.dist-info}/LICENSE.txt +0 -0
  33. {kiln_ai-0.0.4.dist-info → kiln_ai-0.5.0.dist-info}/WHEEL +0 -0
@@ -0,0 +1,170 @@
1
+ import getpass
2
+ import os
3
+ import threading
4
+ from pathlib import Path
5
+ from typing import Any, Callable, Dict, Optional
6
+
7
+ import yaml
8
+
9
+
10
+ class ConfigProperty:
11
+ def __init__(
12
+ self,
13
+ type_: type,
14
+ default: Any = None,
15
+ env_var: Optional[str] = None,
16
+ default_lambda: Optional[Callable[[], Any]] = None,
17
+ sensitive: bool = False,
18
+ ):
19
+ self.type = type_
20
+ self.default = default
21
+ self.env_var = env_var
22
+ self.default_lambda = default_lambda
23
+ self.sensitive = sensitive
24
+
25
+
26
+ class Config:
27
+ _shared_instance = None
28
+
29
+ def __init__(self, properties: Dict[str, ConfigProperty] | None = None):
30
+ self._properties: Dict[str, ConfigProperty] = properties or {
31
+ "user_id": ConfigProperty(
32
+ str,
33
+ env_var="KILN_USER_ID",
34
+ default_lambda=_get_user_id,
35
+ ),
36
+ "autosave_runs": ConfigProperty(
37
+ bool,
38
+ env_var="KILN_AUTOSAVE_RUNS",
39
+ default=True,
40
+ ),
41
+ "open_ai_api_key": ConfigProperty(
42
+ str,
43
+ env_var="OPENAI_API_KEY",
44
+ sensitive=True,
45
+ ),
46
+ "groq_api_key": ConfigProperty(
47
+ str,
48
+ env_var="GROQ_API_KEY",
49
+ sensitive=True,
50
+ ),
51
+ "ollama_base_url": ConfigProperty(
52
+ str,
53
+ env_var="OLLAMA_BASE_URL",
54
+ ),
55
+ "bedrock_access_key": ConfigProperty(
56
+ str,
57
+ env_var="AWS_ACCESS_KEY_ID",
58
+ sensitive=True,
59
+ ),
60
+ "bedrock_secret_key": ConfigProperty(
61
+ str,
62
+ env_var="AWS_SECRET_ACCESS_KEY",
63
+ sensitive=True,
64
+ ),
65
+ "open_router_api_key": ConfigProperty(
66
+ str,
67
+ env_var="OPENROUTER_API_KEY",
68
+ sensitive=True,
69
+ ),
70
+ "projects": ConfigProperty(
71
+ list,
72
+ default_lambda=lambda: [],
73
+ ),
74
+ }
75
+ self._settings = self.load_settings()
76
+
77
+ @classmethod
78
+ def shared(cls):
79
+ if cls._shared_instance is None:
80
+ cls._shared_instance = cls()
81
+ return cls._shared_instance
82
+
83
+ # Get a value, mockable for testing
84
+ def get_value(self, name: str) -> Any:
85
+ try:
86
+ return self.__getattr__(name)
87
+ except AttributeError:
88
+ return None
89
+
90
+ def __getattr__(self, name: str) -> Any:
91
+ if name == "_properties":
92
+ return super().__getattribute__("_properties")
93
+ if name not in self._properties:
94
+ return super().__getattribute__(name)
95
+
96
+ property_config = self._properties[name]
97
+
98
+ # Check if the value is in settings
99
+ if name in self._settings:
100
+ return property_config.type(self._settings[name])
101
+
102
+ # Check environment variable
103
+ if property_config.env_var and property_config.env_var in os.environ:
104
+ value = os.environ[property_config.env_var]
105
+ return property_config.type(value)
106
+
107
+ # Use default value or default_lambda
108
+ if property_config.default_lambda:
109
+ value = property_config.default_lambda()
110
+ else:
111
+ value = property_config.default
112
+
113
+ return property_config.type(value)
114
+
115
+ def __setattr__(self, name, value):
116
+ if name in ("_properties", "_settings"):
117
+ super().__setattr__(name, value)
118
+ elif name in self._properties:
119
+ self.update_settings({name: value})
120
+ else:
121
+ raise AttributeError(f"Config has no attribute '{name}'")
122
+
123
+ @classmethod
124
+ def settings_path(cls, create=True):
125
+ settings_dir = os.path.join(Path.home(), ".kiln_ai")
126
+ if create and not os.path.exists(settings_dir):
127
+ os.makedirs(settings_dir)
128
+ return os.path.join(settings_dir, "settings.yaml")
129
+
130
+ @classmethod
131
+ def load_settings(cls):
132
+ if not os.path.isfile(cls.settings_path(create=False)):
133
+ return {}
134
+ with open(cls.settings_path(), "r") as f:
135
+ settings = yaml.safe_load(f.read()) or {}
136
+ return settings
137
+
138
+ def settings(self, hide_sensitive=False):
139
+ if hide_sensitive:
140
+ return {
141
+ k: "[hidden]"
142
+ if k in self._properties and self._properties[k].sensitive
143
+ else v
144
+ for k, v in self._settings.items()
145
+ }
146
+ return self._settings
147
+
148
+ def save_setting(self, name: str, value: Any):
149
+ self.update_settings({name: value})
150
+
151
+ def update_settings(self, new_settings: Dict[str, Any]):
152
+ # Lock to prevent race conditions in multi-threaded scenarios
153
+ with threading.Lock():
154
+ # Fresh load to avoid clobbering changes from other instances
155
+ current_settings = self.load_settings()
156
+ current_settings.update(new_settings)
157
+ # remove None values
158
+ current_settings = {
159
+ k: v for k, v in current_settings.items() if v is not None
160
+ }
161
+ with open(self.settings_path(), "w") as f:
162
+ yaml.dump(current_settings, f)
163
+ self._settings = current_settings
164
+
165
+
166
+ def _get_user_id():
167
+ try:
168
+ return getpass.getuser() or "unknown_user"
169
+ except Exception:
170
+ return "unknown_user"
@@ -0,0 +1,5 @@
1
+ import re
2
+
3
+
4
+ def snake_case(s: str) -> str:
5
+ return re.sub(r"(?<!^)(?=[A-Z])", "_", s).lower()
@@ -0,0 +1,245 @@
1
+ import getpass
2
+ import os
3
+ from unittest.mock import patch
4
+
5
+ import pytest
6
+ import yaml
7
+ from kiln_ai.utils.config import Config, ConfigProperty, _get_user_id
8
+
9
+
10
+ @pytest.fixture
11
+ def mock_yaml_file(tmp_path):
12
+ yaml_file = tmp_path / "test_settings.yaml"
13
+ return str(yaml_file)
14
+
15
+
16
+ @pytest.fixture
17
+ def config_with_yaml(mock_yaml_file):
18
+ with patch(
19
+ "kiln_ai.utils.config.Config.settings_path",
20
+ return_value=mock_yaml_file,
21
+ ):
22
+ yield Config(
23
+ properties={
24
+ "example_property": ConfigProperty(
25
+ str, default="default_value", env_var="EXAMPLE_PROPERTY"
26
+ ),
27
+ "int_property": ConfigProperty(int, default=0),
28
+ }
29
+ )
30
+
31
+
32
+ @pytest.fixture(autouse=True)
33
+ def reset_config():
34
+ Config._shared_instance = None
35
+ yield
36
+ Config._shared_instance = None
37
+
38
+
39
+ def test_shared_instance():
40
+ config1 = Config.shared()
41
+ config2 = Config.shared()
42
+ assert config1 is config2
43
+
44
+
45
+ def test_property_default_value(config_with_yaml):
46
+ config = config_with_yaml
47
+ assert config.example_property == "default_value"
48
+
49
+
50
+ def test_property_env_var(reset_config, config_with_yaml):
51
+ os.environ["EXAMPLE_PROPERTY"] = "env_value"
52
+ config = config_with_yaml
53
+ assert config.example_property == "env_value"
54
+ del os.environ["EXAMPLE_PROPERTY"]
55
+
56
+
57
+ def test_property_setter(config_with_yaml):
58
+ config = config_with_yaml
59
+ config.example_property = "new_value"
60
+ assert config.example_property == "new_value"
61
+
62
+
63
+ def test_nonexistent_property(config_with_yaml):
64
+ config = config_with_yaml
65
+ with pytest.raises(AttributeError):
66
+ config.nonexistent_property
67
+
68
+
69
+ def test_property_type_conversion(config_with_yaml):
70
+ config = config_with_yaml
71
+ config = Config(properties={"int_property": ConfigProperty(int, default="42")})
72
+ assert isinstance(config.int_property, int)
73
+ assert config.int_property == 42
74
+
75
+
76
+ def test_property_priority(config_with_yaml):
77
+ os.environ["EXAMPLE_PROPERTY"] = "env_value"
78
+ config = config_with_yaml
79
+
80
+ # Environment variable takes precedence over default
81
+ assert config.example_property == "env_value"
82
+
83
+ # Setter takes precedence over environment variable
84
+ config.example_property = "new_value"
85
+ assert config.example_property == "new_value"
86
+
87
+ del os.environ["EXAMPLE_PROPERTY"]
88
+
89
+
90
+ def test_default_lambda(config_with_yaml):
91
+ config = config_with_yaml
92
+
93
+ def default_lambda():
94
+ return "lambda_value"
95
+
96
+ config = Config(
97
+ properties={
98
+ "lambda_property": ConfigProperty(str, default_lambda=default_lambda)
99
+ }
100
+ )
101
+
102
+ assert config.lambda_property == "lambda_value"
103
+
104
+
105
+ def test_get_user_id_none(monkeypatch):
106
+ monkeypatch.setattr(getpass, "getuser", lambda: None)
107
+ assert _get_user_id() == "unknown_user"
108
+
109
+
110
+ def test_get_user_id_exception(monkeypatch):
111
+ def mock_getuser():
112
+ raise Exception("Test exception")
113
+
114
+ monkeypatch.setattr(getpass, "getuser", mock_getuser)
115
+ assert _get_user_id() == "unknown_user"
116
+
117
+
118
+ def test_get_user_id_valid(monkeypatch):
119
+ monkeypatch.setattr(getpass, "getuser", lambda: "test_user")
120
+ assert _get_user_id() == "test_user"
121
+
122
+
123
+ def test_user_id_default(config_with_yaml):
124
+ # assert Config.shared().user_id == "scosman"
125
+ assert len(Config.shared().user_id) > 0
126
+
127
+
128
+ def test_yaml_persistence(config_with_yaml, mock_yaml_file):
129
+ # Set a value
130
+ config_with_yaml.example_property = "yaml_value"
131
+
132
+ # Check that the value was saved to the YAML file
133
+ with open(mock_yaml_file, "r") as f:
134
+ saved_settings = yaml.safe_load(f)
135
+ assert saved_settings["example_property"] == "yaml_value"
136
+
137
+ # Create a new config instance to test loading from YAML
138
+ new_config = Config(
139
+ properties={
140
+ "example_property": ConfigProperty(
141
+ str, default="default_value", env_var="EXAMPLE_PROPERTY"
142
+ ),
143
+ }
144
+ )
145
+
146
+ # Check that the value is loaded from YAML
147
+ assert new_config.example_property == "yaml_value"
148
+
149
+ # Set an environment variable to check that yaml takes priority
150
+ os.environ["EXAMPLE_PROPERTY"] = "env_value"
151
+
152
+ # Check that the YAML value takes priority
153
+ assert new_config.example_property == "yaml_value"
154
+
155
+ # Clean up the environment variable
156
+ del os.environ["EXAMPLE_PROPERTY"]
157
+
158
+
159
+ def test_yaml_type_conversion(config_with_yaml, mock_yaml_file):
160
+ # Set an integer value
161
+ config_with_yaml.int_property = 42
162
+
163
+ # Check that the value was saved to the YAML file
164
+ with open(mock_yaml_file, "r") as f:
165
+ saved_settings = yaml.safe_load(f)
166
+ assert saved_settings["int_property"] == 42
167
+
168
+ # Create a new config instance to test loading and type conversion from YAML
169
+ new_config = Config(
170
+ properties={
171
+ "int_property": ConfigProperty(int, default=0),
172
+ }
173
+ )
174
+
175
+ # Check that the value is loaded from YAML and converted to int
176
+ assert new_config.int_property == 42
177
+ assert isinstance(new_config.int_property, int)
178
+
179
+
180
+ def test_settings_hide_sensitive():
181
+ config = Config(
182
+ {
183
+ "public_key": ConfigProperty(str, default="public_value"),
184
+ "secret_key": ConfigProperty(str, default="secret_value", sensitive=True),
185
+ }
186
+ )
187
+
188
+ # Set values
189
+ config.public_key = "public_test"
190
+ config.secret_key = "secret_test"
191
+
192
+ # Test without hiding sensitive data
193
+ visible_settings = config.settings(hide_sensitive=False)
194
+ assert visible_settings == {
195
+ "public_key": "public_test",
196
+ "secret_key": "secret_test",
197
+ }
198
+
199
+ # Test with hiding sensitive data
200
+ hidden_settings = config.settings(hide_sensitive=True)
201
+ assert hidden_settings == {"public_key": "public_test", "secret_key": "[hidden]"}
202
+
203
+
204
+ def test_list_property(config_with_yaml, mock_yaml_file):
205
+ # Add a list property to the config
206
+ config_with_yaml._properties["list_property"] = ConfigProperty(list, default=[])
207
+
208
+ # Set initial values
209
+ config_with_yaml.list_property = ["item1", "item2"]
210
+
211
+ # Check that the property returns a list
212
+ assert isinstance(config_with_yaml.list_property, list)
213
+ assert config_with_yaml.list_property == ["item1", "item2"]
214
+
215
+ # Update the list
216
+ config_with_yaml.list_property = ["item1", "item2", "item3"]
217
+ assert config_with_yaml.list_property == ["item1", "item2", "item3"]
218
+
219
+ # Check that the value was saved to the YAML file
220
+ with open(mock_yaml_file, "r") as f:
221
+ saved_settings = yaml.safe_load(f)
222
+ assert saved_settings["list_property"] == ["item1", "item2", "item3"]
223
+
224
+ # Create a new config instance to test loading from YAML
225
+ new_config = Config(
226
+ properties={
227
+ "list_property": ConfigProperty(list, default=[]),
228
+ }
229
+ )
230
+
231
+ # Check that the value is loaded from YAML and is a list
232
+ assert isinstance(new_config.list_property, list)
233
+ assert new_config.list_property == ["item1", "item2", "item3"]
234
+
235
+
236
+ def test_stale_values_bug(config_with_yaml):
237
+ assert config_with_yaml.example_property == "default_value"
238
+
239
+ # Simulate updating the settings file with set_attr
240
+ config_with_yaml.example_property = "second_value"
241
+ assert config_with_yaml.example_property == "second_value"
242
+
243
+ # Simulate updating the settings file with set_settings
244
+ config_with_yaml.update_settings({"example_property": "third_value"})
245
+ assert config_with_yaml.example_property == "third_value"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: kiln-ai
3
- Version: 0.0.4
3
+ Version: 0.5.0
4
4
  Summary: Kiln AI
5
5
  Home-page: https://kiln-ai.com
6
6
  License: Proprietary
@@ -16,3 +16,22 @@ Classifier: Programming Language :: Python :: 3.12
16
16
  Project-URL: Bug Tracker, https://github.com/Kiln-AI/kiln/issues
17
17
  Project-URL: Documentation, https://github.com/Kiln-AI/kiln#readme
18
18
  Project-URL: Repository, https://github.com/Kiln-AI/kiln
19
+ Description-Content-Type: text/markdown
20
+
21
+ # kiln_ai
22
+
23
+ [![PyPI - Version](https://img.shields.io/pypi/v/kiln-ai.svg)](https://pypi.org/project/kiln-ai)
24
+ [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/kiln-ai.svg)](https://pypi.org/project/kiln-ai)
25
+
26
+ ---
27
+
28
+ ## Table of Contents
29
+
30
+ - [Installation](#installation)
31
+
32
+ ## Installation
33
+
34
+ ```console
35
+ pip install kiln_ai
36
+ ```
37
+
@@ -0,0 +1,29 @@
1
+ kiln_ai/adapters/base_adapter.py,sha256=kNQgi_wK3qEwHciN0puiupUoBLq9uxhj-o5chikoLbU,5524
2
+ kiln_ai/adapters/langchain_adapters.py,sha256=AER7ltCyHJQf-SH3pXAWbtKgUrw2bKZAcqU15ZO_x2U,4992
3
+ kiln_ai/adapters/ml_model_list.py,sha256=Lv5T7xlPGnOlxA6DJEPHds9f2FIQ8dq-kqgOgv6G-T8,15036
4
+ kiln_ai/adapters/prompt_builders.py,sha256=O0Z5v3hm0C2Cmx-9ugR-L4ISijakGB4t5nem3N7J65w,4308
5
+ kiln_ai/adapters/repair/repair_task.py,sha256=PXcGyTo-_ChzdutwnUoMNs0QAUHlqqjGGiq03z38jag,3296
6
+ kiln_ai/adapters/repair/test_repair_task.py,sha256=T-1L-YvaWx-ZMOv-brtdSsUgGeNR1fkgb6x8c0lMr4w,7818
7
+ kiln_ai/adapters/test_langchain_adapter.py,sha256=rKC7_SxV_pQmImCLo-4tBEiOJ4k6uf5uRzOgxChp-8w,1875
8
+ kiln_ai/adapters/test_ml_model_list.py,sha256=I6aY_MLkyAGRz6VE7Gd_pJAgKD3CUrc5NrG7VS8BUQc,2806
9
+ kiln_ai/adapters/test_prompt_adaptors.py,sha256=OkQVbODIiZUUxbb-WqLQad3rWYctQzbOFOe6b8p4JCo,6032
10
+ kiln_ai/adapters/test_prompt_builders.py,sha256=R2TLRZOrHRUShLMANZ4AJB5ZGdDSegD-J3geVM_dy90,10639
11
+ kiln_ai/adapters/test_saving_adapter_results.py,sha256=Y1NV4EGnt3KO6B6Gmzo4SIS8fjUZAB1jm64bFU5fS_s,6040
12
+ kiln_ai/adapters/test_structured_output.py,sha256=OQ7yfjAmZd53CEylckhiPzJehZ9NF5Vbko6GCdwQGAg,8754
13
+ kiln_ai/datamodel/__init__.py,sha256=1iUYcXtgnEPonQfYgDtiDaixs_h5MXl03l4nNHZzBjo,13574
14
+ kiln_ai/datamodel/basemodel.py,sha256=-aE5UVwQu1JUJEwjSKV-7u6faxpYZruL_ucRrhA5Y4Q,14559
15
+ kiln_ai/datamodel/json_schema.py,sha256=v-sbC7SuBpzoMKUKNvrBHhVjGxfVzcICcxDJZvkYHc4,1382
16
+ kiln_ai/datamodel/test_basemodel.py,sha256=GRnMWd16Tt-9GGZZ6UIDKiaGt5nLQxcIVkEQxuXGfS4,8633
17
+ kiln_ai/datamodel/test_datasource.py,sha256=vS-DFCKAdLJ8ltTH79ws8tjw8WTKhCQ8amMvKGyEsFY,3264
18
+ kiln_ai/datamodel/test_example_models.py,sha256=9uNi-ZCNMWrhqS0pTDb3jUHlHRTKQJZx5Hsylxb6ljE,20934
19
+ kiln_ai/datamodel/test_json_schema.py,sha256=lYTSx2NiywjxRIRT6k6AcM_nmeAymkUvzhsfFbFml04,3198
20
+ kiln_ai/datamodel/test_models.py,sha256=SZlItqmxjbh8H-0ANoxbayfSppLuBGpRHeQp6mpUcjo,5929
21
+ kiln_ai/datamodel/test_nested_save.py,sha256=9BHh0iVqKY_Ug87uGhTyiRJ9frwy1mUhaqnyMD2oX5Y,5587
22
+ kiln_ai/datamodel/test_output_rating.py,sha256=8PHvxh6jGRXNedl8oSTtr8Ur1lwnJ28xl47preX7X9c,2646
23
+ kiln_ai/utils/config.py,sha256=jXUB8lwFkxLNEaizwIsoeFLg1BwjWr39-5KdEGF37Bg,5424
24
+ kiln_ai/utils/formatting.py,sha256=VtB9oag0lOGv17dwT7OPX_3HzBfaU9GsLH-iLete0yM,97
25
+ kiln_ai/utils/test_config.py,sha256=22i5t7tElOoVYfgR_Nmif3QriPt9yAqlK8e5CgiCCUM,7410
26
+ kiln_ai-0.5.0.dist-info/LICENSE.txt,sha256=-AhuIX-CMdNGJNj74C29e9cKKmsh-1PBPINCsNvwAeg,82
27
+ kiln_ai-0.5.0.dist-info/METADATA,sha256=Hz7Ix5qO9AxWjd8aIDhmiNeNlEw1xp5auVPgq5KKOe8,1107
28
+ kiln_ai-0.5.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
29
+ kiln_ai-0.5.0.dist-info/RECORD,,
kiln_ai/__init.__.py DELETED
@@ -1,3 +0,0 @@
1
- import coreadd
2
-
3
- __all__ = ["coreadd"]
kiln_ai/coreadd.py DELETED
@@ -1,3 +0,0 @@
1
- # TODO_P0: remove example function
2
- def add(a, b) -> int:
3
- return a + b + 1
@@ -1,15 +0,0 @@
1
- from pydantic import BaseModel
2
- from typing import Optional
3
-
4
-
5
- class KilnProject(BaseModel):
6
- version: int = 1
7
- name: str
8
- path: Optional[str] = None
9
-
10
- def __init__(self, name: str, path: Optional[str] = None):
11
- # TODO: learn about pydantic init
12
- super().__init__(name=name, path=path)
13
- if path is not None and name is not None:
14
- # path and name are mutually exclusive for constructor, name comes from path if passed
15
- raise ValueError("path and name are mutually exclusive")
@@ -1,8 +0,0 @@
1
- kiln_ai/__init.__.py,sha256=4OOrqjKxMymEivpmr_q0UPol0N_zJhgurRwxxShFTfQ,38
2
- kiln_ai/coreadd.py,sha256=2VSwMUN89ewbmsn-uUUzRUNq1otD5DTjmM_Z_XG4Mc8,78
3
- kiln_ai/datamodel/__init__.py,sha256=EHMToTvBMGWp6vEbBpKICmDex77su8mLgbFBVqNJVGA,60
4
- kiln_ai/datamodel/project.py,sha256=IH_skMyfZ-ieeYTzdMc0IrA8MSKvfEQij9HLkYXuwsM,528
5
- kiln_ai-0.0.4.dist-info/LICENSE.txt,sha256=-AhuIX-CMdNGJNj74C29e9cKKmsh-1PBPINCsNvwAeg,82
6
- kiln_ai-0.0.4.dist-info/METADATA,sha256=IldGPZe9E1yAZB56xg12815PA1ebEPaLUdWvIi6HrjM,731
7
- kiln_ai-0.0.4.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
8
- kiln_ai-0.0.4.dist-info/RECORD,,