digitalkin 0.2.19__py3-none-any.whl → 0.2.21__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.
digitalkin/__version__.py CHANGED
@@ -5,4 +5,4 @@ from importlib.metadata import PackageNotFoundError, version
5
5
  try:
6
6
  __version__ = version("digitalkin")
7
7
  except PackageNotFoundError:
8
- __version__ = "0.2.19"
8
+ __version__ = "0.2.21"
@@ -100,6 +100,9 @@ class ModuleServicer(module_service_pb2_grpc.ModuleServiceServicer, ArgParser):
100
100
  ServicerError: if the setup data is not returned or job creation fails.
101
101
  """
102
102
  logger.info("ConfigSetupVersion called for module: '%s'", self.module_class.__name__)
103
+ logger.info(
104
+ "Context : %s, setup_version: %s, mission_id: %s", context, request.setup_version, request.mission_id
105
+ )
103
106
  # Process the module input
104
107
  # TODO: Secret should be used here as well
105
108
  setup_version = request.setup_version
@@ -121,6 +124,7 @@ class ModuleServicer(module_service_pb2_grpc.ModuleServiceServicer, ArgParser):
121
124
  job_id = await self.job_manager.create_config_setup_instance_job(
122
125
  config_setup_data,
123
126
  request.mission_id,
127
+ setup_version.setup_id,
124
128
  setup_version.id,
125
129
  )
126
130
 
@@ -156,6 +160,7 @@ class ModuleServicer(module_service_pb2_grpc.ModuleServiceServicer, ArgParser):
156
160
  ServicerError: the necessary query didn't work.
157
161
  """
158
162
  logger.info("StartModule called for module: '%s'", self.module_class.__name__)
163
+ logger.info("Context : %s, setup_id: %s, mission_id: %s", context, request.setup_id, request.mission_id)
159
164
  # Process the module input
160
165
  # TODO: Check failure of input data format
161
166
  input_data = self.module_class.create_input_model(dict(request.input.items()))
@@ -177,6 +182,7 @@ class ModuleServicer(module_service_pb2_grpc.ModuleServiceServicer, ArgParser):
177
182
  input_data,
178
183
  setup_data,
179
184
  mission_id=request.mission_id,
185
+ setup_id=setup_data_class.current_setup_version.setup_id,
180
186
  setup_version_id=setup_data_class.current_setup_version.id,
181
187
  )
182
188
 
@@ -54,7 +54,6 @@ class BaseModule( # noqa: PLR0904
54
54
  description: str
55
55
 
56
56
  setup_format: type[SetupModelT]
57
-
58
57
  input_format: type[InputModelT]
59
58
  output_format: type[OutputModelT]
60
59
  secret_format: type[SecretModelT]
@@ -77,21 +76,38 @@ class BaseModule( # noqa: PLR0904
77
76
  snapshot: SnapshotStrategy
78
77
  storage: StorageStrategy
79
78
 
79
+ # runtime params
80
+ job_id: str
81
+ mission_id: str
82
+ setup_id: str
83
+ setup_version_id: str
84
+ _status: ModuleStatus
85
+ _task: asyncio.Task | None
86
+
80
87
  def _init_strategies(self) -> None:
81
88
  """Initialize the services configuration."""
82
89
  for service_name in self.services_config.valid_strategy_names():
83
- service = self.services_config.init_strategy(service_name, self.mission_id, self.setup_version_id)
90
+ service = self.services_config.init_strategy(
91
+ service_name,
92
+ self.mission_id,
93
+ self.setup_id,
94
+ self.setup_version_id,
95
+ )
84
96
  setattr(self, service_name, service)
85
97
 
86
98
  def __init__(
87
99
  self,
88
100
  job_id: str,
89
101
  mission_id: str,
102
+ setup_id: str,
90
103
  setup_version_id: str,
91
104
  ) -> None:
92
105
  """Initialize the module."""
93
106
  self.job_id: str = job_id
94
107
  self.mission_id: str = mission_id
108
+ # Setup reference needed for the overall Kin scope as the filesystem context
109
+ self.setup_id: str = setup_id
110
+ # SetupVersion reference needed for the precise Kin scope as the cost
95
111
  self.setup_version_id: str = setup_version_id
96
112
  self._status = ModuleStatus.CREATED
97
113
  self._task: asyncio.Task | None = None
@@ -87,6 +87,7 @@ class BaseJobManager(abc.ABC, Generic[InputModelT, SetupModelT]):
87
87
  input_data: InputModelT,
88
88
  setup_data: SetupModelT,
89
89
  mission_id: str,
90
+ setup_id: str,
90
91
  setup_version_id: str,
91
92
  ) -> str:
92
93
  """Create and start a new job for the module's instance.
@@ -95,7 +96,8 @@ class BaseJobManager(abc.ABC, Generic[InputModelT, SetupModelT]):
95
96
  input_data: The input data required to start the job.
96
97
  setup_data: The setup configuration for the module.
97
98
  mission_id: The mission ID associated with the job.
98
- setup_version_id: The setup ID associated with the module.
99
+ setup_id: The setup ID.
100
+ setup_version_id: The setup version ID associated with the module.
99
101
 
100
102
  Returns:
101
103
  str: The unique identifier (job ID) of the created job.
@@ -121,6 +123,7 @@ class BaseJobManager(abc.ABC, Generic[InputModelT, SetupModelT]):
121
123
  self,
122
124
  config_setup_data: SetupModelT,
123
125
  mission_id: str,
126
+ setup_id: str,
124
127
  setup_version_id: str,
125
128
  ) -> str:
126
129
  """Create and start a new module job.
@@ -131,7 +134,8 @@ class BaseJobManager(abc.ABC, Generic[InputModelT, SetupModelT]):
131
134
  Args:
132
135
  config_setup_data: The input data required to start the job.
133
136
  mission_id: The mission ID associated with the job.
134
- setup_version_id: The setup ID.
137
+ setup_id: The setup ID.
138
+ setup_version_id: The setup version ID.
135
139
 
136
140
  Returns:
137
141
  str: The unique identifier (job ID) of the created job.
@@ -71,6 +71,7 @@ class SingleJobManager(BaseJobManager, Generic[InputModelT, SetupModelT]):
71
71
  self,
72
72
  config_setup_data: SetupModelT,
73
73
  mission_id: str,
74
+ setup_id: str,
74
75
  setup_version_id: str,
75
76
  ) -> str:
76
77
  """Create and start a new module setup configuration job.
@@ -82,6 +83,7 @@ class SingleJobManager(BaseJobManager, Generic[InputModelT, SetupModelT]):
82
83
  config_setup_data: The input data required to start the job.
83
84
  setup_data: The setup configuration for the module.
84
85
  mission_id: The mission ID associated with the job.
86
+ setup_id: The setup ID associated with the module.
85
87
  setup_version_id: The setup ID.
86
88
 
87
89
  Returns:
@@ -92,7 +94,7 @@ class SingleJobManager(BaseJobManager, Generic[InputModelT, SetupModelT]):
92
94
  """
93
95
  job_id = str(uuid.uuid4())
94
96
  # TODO: Ensure the job_id is unique.
95
- module = self.module_class(job_id, mission_id=mission_id, setup_version_id=setup_version_id)
97
+ module = self.module_class(job_id, mission_id=mission_id, setup_id=setup_id, setup_version_id=setup_version_id)
96
98
  self.modules[job_id] = module
97
99
  self.queues[job_id] = asyncio.Queue()
98
100
 
@@ -176,6 +178,7 @@ class SingleJobManager(BaseJobManager, Generic[InputModelT, SetupModelT]):
176
178
  input_data: InputModelT,
177
179
  setup_data: SetupModelT,
178
180
  mission_id: str,
181
+ setup_id: str,
179
182
  setup_version_id: str,
180
183
  ) -> str:
181
184
  """Create and start a new module job.
@@ -187,7 +190,8 @@ class SingleJobManager(BaseJobManager, Generic[InputModelT, SetupModelT]):
187
190
  input_data: The input data required to start the job.
188
191
  setup_data: The setup configuration for the module.
189
192
  mission_id: The mission ID associated with the job.
190
- setup_version_id: The setup ID associated with the module.
193
+ setup_id: The setup ID associated with the module.
194
+ setup_version_id: The setup Version ID associated with the module.
191
195
 
192
196
  Returns:
193
197
  str: The unique identifier (job ID) of the created job.
@@ -197,7 +201,12 @@ class SingleJobManager(BaseJobManager, Generic[InputModelT, SetupModelT]):
197
201
  """
198
202
  job_id = str(uuid.uuid4())
199
203
  # TODO: Ensure the job_id is unique.
200
- module = self.module_class(job_id, mission_id=mission_id, setup_version_id=setup_version_id)
204
+ module = self.module_class(
205
+ job_id,
206
+ mission_id=mission_id,
207
+ setup_id=setup_id,
208
+ setup_version_id=setup_version_id,
209
+ )
201
210
  self.modules[job_id] = module
202
211
  self.queues[job_id] = asyncio.Queue()
203
212
  callback = await self.job_specific_callback(self.add_to_queue, job_id)
@@ -132,6 +132,7 @@ async def send_message_to_stream(job_id: str, output_data: OutputModelT) -> None
132
132
  @TASKIQ_BROKER.task
133
133
  async def run_start_module(
134
134
  mission_id: str,
135
+ setup_id: str,
135
136
  setup_version_id: str,
136
137
  module_class: type[BaseModule],
137
138
  services_mode: ServicesMode,
@@ -143,6 +144,7 @@ async def run_start_module(
143
144
 
144
145
  Args:
145
146
  mission_id: str,
147
+ setup_id: The setup ID associated with the module.
146
148
  setup_version_id: The setup ID associated with the module.
147
149
  module_class: type[BaseModule],
148
150
  services_mode: ServicesMode,
@@ -161,7 +163,7 @@ async def run_start_module(
161
163
 
162
164
  job_id = context.message.task_id
163
165
  callback = await BaseJobManager.job_specific_callback(send_message_to_stream, job_id)
164
- module = module_class(job_id, mission_id=mission_id, setup_version_id=setup_version_id)
166
+ module = module_class(job_id, mission_id=mission_id, setup_id=setup_id, setup_version_id=setup_version_id)
165
167
 
166
168
  await module.start(
167
169
  input_data,
@@ -176,6 +178,7 @@ async def run_start_module(
176
178
  @TASKIQ_BROKER.task
177
179
  async def run_config_module(
178
180
  mission_id: str,
181
+ setup_id: str,
179
182
  setup_version_id: str,
180
183
  module_class: type[BaseModule],
181
184
  services_mode: ServicesMode,
@@ -186,6 +189,7 @@ async def run_config_module(
186
189
 
187
190
  Args:
188
191
  mission_id: str,
192
+ setup_id: The setup ID associated with the module.
189
193
  setup_version_id: The setup ID associated with the module.
190
194
  module_class: type[BaseModule],
191
195
  services_mode: ServicesMode,
@@ -204,7 +208,7 @@ async def run_config_module(
204
208
 
205
209
  job_id = context.message.task_id
206
210
  callback = await BaseJobManager.job_specific_callback(send_message_to_stream, job_id)
207
- module = module_class(job_id, mission_id=mission_id, setup_version_id=setup_version_id)
211
+ module = module_class(job_id, mission_id=mission_id, setup_id=setup_id, setup_version_id=setup_version_id)
208
212
 
209
213
  await module.start_config_setup(
210
214
  module_class.create_config_setup_model(config_setup_data),
@@ -136,6 +136,7 @@ class TaskiqJobManager(BaseJobManager, Generic[InputModelT, SetupModelT]):
136
136
  self,
137
137
  config_setup_data: SetupModelT,
138
138
  mission_id: str,
139
+ setup_id: str,
139
140
  setup_version_id: str,
140
141
  ) -> str:
141
142
  """Create and start a new module setup configuration job.
@@ -147,6 +148,7 @@ class TaskiqJobManager(BaseJobManager, Generic[InputModelT, SetupModelT]):
147
148
  config_setup_data: The input data required to start the job.
148
149
  setup_data: The setup configuration for the module.
149
150
  mission_id: The mission ID associated with the job.
151
+ setup_id: The setup ID associated with the module.
150
152
  setup_version_id: The setup ID.
151
153
 
152
154
  Returns:
@@ -168,6 +170,7 @@ class TaskiqJobManager(BaseJobManager, Generic[InputModelT, SetupModelT]):
168
170
 
169
171
  running_task: AsyncTaskiqTask[Any] = await task.kiq(
170
172
  mission_id,
173
+ setup_id,
171
174
  setup_version_id,
172
175
  self.module_class,
173
176
  self.services_mode,
@@ -221,6 +224,7 @@ class TaskiqJobManager(BaseJobManager, Generic[InputModelT, SetupModelT]):
221
224
  input_data: InputModelT,
222
225
  setup_data: SetupModelT,
223
226
  mission_id: str,
227
+ setup_id: str,
224
228
  setup_version_id: str,
225
229
  ) -> str:
226
230
  """Launches the module_task in Taskiq, returns the Taskiq task id as job_id.
@@ -229,6 +233,7 @@ class TaskiqJobManager(BaseJobManager, Generic[InputModelT, SetupModelT]):
229
233
  input_data: Input data for the module
230
234
  setup_data: Setup data for the module
231
235
  mission_id: Mission ID for the module
236
+ setup_id: The setup ID associated with the module.
232
237
  setup_version_id: The setup ID associated with the module.
233
238
 
234
239
  Returns:
@@ -245,6 +250,7 @@ class TaskiqJobManager(BaseJobManager, Generic[InputModelT, SetupModelT]):
245
250
 
246
251
  running_task: AsyncTaskiqTask[Any] = await task.kiq(
247
252
  mission_id,
253
+ setup_id,
248
254
  setup_version_id,
249
255
  self.module_class,
250
256
  self.services_mode,
@@ -9,12 +9,14 @@ class BaseStrategy(ABC):
9
9
  This class defines the interface for all strategies.
10
10
  """
11
11
 
12
- def __init__(self, mission_id: str, setup_version_id: str) -> None:
12
+ def __init__(self, mission_id: str, setup_id: str, setup_version_id: str) -> None:
13
13
  """Initialize the strategy.
14
14
 
15
15
  Args:
16
16
  mission_id: The ID of the mission this strategy is associated with
17
+ setup_id: The ID of the setup this strategy is associated with
17
18
  setup_version_id: The ID of the setup version this strategy is associated with
18
19
  """
19
20
  self.mission_id: str = mission_id
21
+ self.setup_id: str = setup_id
20
22
  self.setup_version_id: str = setup_version_id
@@ -57,15 +57,22 @@ class CostServiceError(Exception):
57
57
  class CostStrategy(BaseStrategy, ABC):
58
58
  """Abstract base class for cost strategies."""
59
59
 
60
- def __init__(self, mission_id: str, setup_version_id: str, config: dict[str, CostConfig]) -> None:
60
+ def __init__(
61
+ self,
62
+ mission_id: str,
63
+ setup_id: str,
64
+ setup_version_id: str,
65
+ config: dict[str, CostConfig],
66
+ ) -> None:
61
67
  """Initialize the strategy.
62
68
 
63
69
  Args:
64
70
  mission_id: The ID of the mission this strategy is associated with
71
+ setup_id: The ID of the setup
65
72
  setup_version_id: The ID of the setup version this strategy is associated with
66
73
  config: Configuration dictionary for the strategy
67
74
  """
68
- super().__init__(mission_id, setup_version_id)
75
+ super().__init__(mission_id, setup_id, setup_version_id)
69
76
  self.config = config
70
77
 
71
78
  @abstractmethod
@@ -15,15 +15,16 @@ from digitalkin.services.cost.cost_strategy import (
15
15
  class DefaultCost(CostStrategy):
16
16
  """Default cost strategy."""
17
17
 
18
- def __init__(self, mission_id: str, setup_version_id: str, config: dict[str, CostConfig]) -> None:
18
+ def __init__(self, mission_id: str, setup_id: str, setup_version_id: str, config: dict[str, CostConfig]) -> None:
19
19
  """Initialize the strategy.
20
20
 
21
21
  Args:
22
22
  mission_id: The ID of the mission this strategy is associated with
23
+ setup_id: The ID of the setup
23
24
  setup_version_id: The ID of the setup version this strategy is associated with
24
25
  config: The configuration dictionary for the cost
25
26
  """
26
- super().__init__(mission_id=mission_id, setup_version_id=setup_version_id, config=config)
27
+ super().__init__(mission_id=mission_id, setup_id=setup_id, setup_version_id=setup_version_id, config=config)
27
28
  self.db: dict[str, list[CostData]] = {}
28
29
 
29
30
  def add(
@@ -57,12 +57,13 @@ class GrpcCost(CostStrategy, GrpcClientWrapper):
57
57
  def __init__(
58
58
  self,
59
59
  mission_id: str,
60
+ setup_id: str,
60
61
  setup_version_id: str,
61
62
  config: dict[str, CostConfig],
62
63
  client_config: ClientConfig,
63
64
  ) -> None:
64
65
  """Initialize the cost."""
65
- super().__init__(mission_id=mission_id, setup_version_id=setup_version_id, config=config)
66
+ super().__init__(mission_id=mission_id, setup_id=setup_id, setup_version_id=setup_version_id, config=config)
66
67
  channel = self._init_channel(client_config)
67
68
  self.stub = cost_service_pb2_grpc.CostServiceStub(channel)
68
69
  logger.debug("Channel client 'Cost' initialized succesfully")
@@ -25,14 +25,15 @@ class DefaultFilesystem(FilesystemStrategy):
25
25
  Files are stored in a temporary directory with proper metadata tracking.
26
26
  """
27
27
 
28
- def __init__(self, mission_id: str, setup_version_id: str) -> None:
28
+ def __init__(self, mission_id: str, setup_id: str, setup_version_id: str) -> None:
29
29
  """Initialize the default filesystem strategy.
30
30
 
31
31
  Args:
32
32
  mission_id: The ID of the mission this strategy is associated with
33
+ setup_id: The ID of the setup
33
34
  setup_version_id: The ID of the setup version this strategy is associated with
34
35
  """
35
- super().__init__(mission_id, setup_version_id)
36
+ super().__init__(mission_id, setup_id, setup_version_id)
36
37
  self.temp_root: str = tempfile.mkdtemp()
37
38
  os.makedirs(self.temp_root, exist_ok=True)
38
39
  self.db: dict[str, FilesystemRecord] = {}
@@ -83,7 +84,6 @@ class DefaultFilesystem(FilesystemStrategy):
83
84
  if (not filters.names or f.name in filters.names)
84
85
  and (not filters.file_ids or f.id in filters.file_ids)
85
86
  and (not filters.file_types or f.file_type in filters.file_types)
86
- and f.context == self.mission_id
87
87
  and (not filters.status or f.status == filters.status)
88
88
  and (not filters.content_type_prefix or f.content_type.startswith(filters.content_type_prefix))
89
89
  and (not filters.min_size_bytes or f.size_bytes >= filters.min_size_bytes)
@@ -118,7 +118,7 @@ class DefaultFilesystem(FilesystemStrategy):
118
118
  for file in files:
119
119
  try:
120
120
  # Check if file with same name exists in the context
121
- context_dir = self._get_context_temp_dir(self.mission_id)
121
+ context_dir = self._get_context_temp_dir(self.setup_id)
122
122
  file_path = os.path.join(context_dir, file.name)
123
123
  if os.path.exists(file_path) and not file.replace_if_exists:
124
124
  msg = f"File with name {file.name} already exists."
@@ -129,7 +129,7 @@ class DefaultFilesystem(FilesystemStrategy):
129
129
  storage_uri = str(Path(file_path).resolve())
130
130
  file_data = FilesystemRecord(
131
131
  id=str(uuid.uuid4()),
132
- context=self.mission_id,
132
+ context=self.setup_id,
133
133
  name=file.name,
134
134
  file_type=file.file_type,
135
135
  content_type=file.content_type or "application/octet-stream",
@@ -138,14 +138,13 @@ class DefaultFilesystem(FilesystemStrategy):
138
138
  metadata=file.metadata,
139
139
  storage_uri=storage_uri,
140
140
  file_url=storage_uri,
141
- status=file.status if hasattr(file, "status") and file.status else "ACTIVE",
141
+ status="ACTIVE",
142
142
  )
143
143
 
144
144
  self.db[file_data.id] = file_data
145
145
  uploaded_files.append(file_data)
146
146
  total_uploaded += 1
147
147
  logger.debug("Uploaded file %s", file_data)
148
-
149
148
  except Exception as e: # noqa: PERF203
150
149
  logger.exception("Error uploading file %s: %s", file.name, e)
151
150
  total_failed += 1
@@ -199,6 +198,8 @@ class DefaultFilesystem(FilesystemStrategy):
199
198
  end_idx = start_idx + list_size
200
199
  paginated_files = filtered_files[start_idx:end_idx]
201
200
 
201
+ logger.critical(f"{filters=} | {paginated_files=}")
202
+
202
203
  if include_content:
203
204
  for file in paginated_files:
204
205
  file.content = Path(file.storage_uri).read_bytes()
@@ -213,6 +214,7 @@ class DefaultFilesystem(FilesystemStrategy):
213
214
  def get_file(
214
215
  self,
215
216
  file_id: str,
217
+ context: Literal["mission", "setup"] = "mission", # noqa: ARG002
216
218
  *,
217
219
  include_content: bool = False,
218
220
  ) -> FilesystemRecord:
@@ -224,6 +226,7 @@ class DefaultFilesystem(FilesystemStrategy):
224
226
 
225
227
  Args:
226
228
  file_id: The ID of the file to be retrieved
229
+ context: The context of the files (mission or setup)
227
230
  include_content: Whether to include file content in response
228
231
 
229
232
  Returns:
@@ -306,7 +309,7 @@ class DefaultFilesystem(FilesystemStrategy):
306
309
  raise FilesystemServiceError(msg)
307
310
 
308
311
  try:
309
- context_dir = self._get_context_temp_dir(self.mission_id)
312
+ context_dir = self._get_context_temp_dir(self.setup_id)
310
313
  file_path = os.path.join(context_dir, file_id)
311
314
  existing_file = self.db[file_id]
312
315
 
@@ -33,6 +33,9 @@ class FilesystemRecord(BaseModel):
33
33
  class FileFilter(BaseModel):
34
34
  """Filter criteria for querying files."""
35
35
 
36
+ context: Literal["mission", "setup"] = Field(
37
+ default="mission", description="The context of the files (mission or setup)"
38
+ )
36
39
  names: list[str] | None = Field(default=None, description="Filter by file names (exact matches)")
37
40
  file_ids: list[str] | None = Field(default=None, description="Filter by file IDs")
38
41
  file_types: (
@@ -90,15 +93,22 @@ class FilesystemStrategy(BaseStrategy, ABC):
90
93
  filtering, and pagination.
91
94
  """
92
95
 
93
- def __init__(self, mission_id: str, setup_version_id: str, config: dict[str, Any] | None = None) -> None:
96
+ def __init__(
97
+ self,
98
+ mission_id: str,
99
+ setup_id: str,
100
+ setup_version_id: str,
101
+ config: dict[str, Any] | None = None,
102
+ ) -> None:
94
103
  """Initialize the strategy.
95
104
 
96
105
  Args:
97
106
  mission_id: The ID of the mission this strategy is associated with
107
+ setup_id: The ID of the setup
98
108
  setup_version_id: The ID of the setup version this strategy is associated with
99
109
  config: Configuration for the filesystem strategy
100
110
  """
101
- super().__init__(mission_id, setup_version_id)
111
+ super().__init__(mission_id, setup_id, setup_version_id)
102
112
  self.config = config
103
113
 
104
114
  @abstractmethod
@@ -123,9 +133,10 @@ class FilesystemStrategy(BaseStrategy, ABC):
123
133
  def get_file(
124
134
  self,
125
135
  file_id: str,
136
+ context: Literal["mission", "setup"] = "mission",
126
137
  *,
127
138
  include_content: bool = False,
128
- ) -> tuple[FilesystemRecord, bytes | None]:
139
+ ) -> FilesystemRecord:
129
140
  """Get a specific file by ID or name.
130
141
 
131
142
  This method fetches detailed information about a single file,
@@ -134,6 +145,7 @@ class FilesystemStrategy(BaseStrategy, ABC):
134
145
 
135
146
  Args:
136
147
  file_id: The ID of the file to be retrieved
148
+ context: The context of the files (mission or setup)
137
149
  include_content: Whether to include file content in response
138
150
 
139
151
  Returns:
@@ -85,25 +85,8 @@ class GrpcFilesystem(FilesystemStrategy, GrpcClientWrapper):
85
85
  except AttributeError:
86
86
  return filesystem_pb2.FileStatus.FILE_STATUS_UNSPECIFIED
87
87
 
88
- def _filter_to_proto(self, filters: FileFilter) -> filesystem_pb2.FileFilter:
89
- """Convert a FileFilter to a FileFilter proto message.
90
-
91
- Args:
92
- filters: The FileFilter to convert
93
-
94
- Returns:
95
- filesystem_pb2.FileFilter: The converted FileFilter proto message
96
- """
97
- return filesystem_pb2.FileFilter(
98
- context=self.mission_id,
99
- **filters.model_dump(exclude={"file_types", "status"}),
100
- file_types=[self._file_type_to_enum(file_type) for file_type in filters.file_types]
101
- if filters.file_types
102
- else None,
103
- status=self._file_status_to_enum(filters.status) if filters.status else None,
104
- )
105
-
106
- def _file_proto_to_data(self, file: filesystem_pb2.File) -> FilesystemRecord:
88
+ @staticmethod
89
+ def _file_proto_to_data(file: filesystem_pb2.File) -> FilesystemRecord:
107
90
  """Convert a File proto message to FilesystemRecord.
108
91
 
109
92
  Args:
@@ -114,7 +97,7 @@ class GrpcFilesystem(FilesystemStrategy, GrpcClientWrapper):
114
97
  """
115
98
  return FilesystemRecord(
116
99
  id=file.file_id,
117
- context=self.mission_id,
100
+ context=file.context,
118
101
  name=file.name,
119
102
  file_type=filesystem_pb2.FileType.Name(file.file_type),
120
103
  content_type=file.content_type,
@@ -127,9 +110,27 @@ class GrpcFilesystem(FilesystemStrategy, GrpcClientWrapper):
127
110
  content=file.content,
128
111
  )
129
112
 
113
+ def _filter_to_proto(self, filters: FileFilter) -> filesystem_pb2.FileFilter:
114
+ """Convert a FileFilter to a FileFilter proto message.
115
+
116
+ Args:
117
+ filters: The FileFilter to convert
118
+
119
+ Returns:
120
+ filesystem_pb2.FileFilter: The converted FileFilter proto message
121
+ """
122
+ return filesystem_pb2.FileFilter(
123
+ **filters.model_dump(exclude={"file_types", "status"}),
124
+ file_types=[self._file_type_to_enum(file_type) for file_type in filters.file_types]
125
+ if filters.file_types
126
+ else None,
127
+ status=self._file_status_to_enum(filters.status) if filters.status else None,
128
+ )
129
+
130
130
  def __init__(
131
131
  self,
132
132
  mission_id: str,
133
+ setup_id: str,
133
134
  setup_version_id: str,
134
135
  client_config: ClientConfig,
135
136
  config: dict[str, Any] | None = None,
@@ -138,11 +139,12 @@ class GrpcFilesystem(FilesystemStrategy, GrpcClientWrapper):
138
139
 
139
140
  Args:
140
141
  mission_id: The ID of the mission this strategy is associated with
142
+ setup_id: The ID of the setup
141
143
  setup_version_id: The ID of the setup version this strategy is associated with
142
144
  client_config: Configuration for the gRPC client connection
143
145
  config: Configuration for the filesystem strategy
144
146
  """
145
- super().__init__(mission_id, setup_version_id, config)
147
+ super().__init__(mission_id, setup_id, setup_version_id, config)
146
148
  self.service_name = "FilesystemService"
147
149
  channel = self._init_channel(client_config)
148
150
  self.stub = filesystem_service_pb2_grpc.FilesystemServiceStub(channel)
@@ -189,6 +191,7 @@ class GrpcFilesystem(FilesystemStrategy, GrpcClientWrapper):
189
191
  def get_file(
190
192
  self,
191
193
  file_id: str,
194
+ context: Literal["mission", "setup"] = "mission",
192
195
  *,
193
196
  include_content: bool = False,
194
197
  ) -> FilesystemRecord:
@@ -196,6 +199,7 @@ class GrpcFilesystem(FilesystemStrategy, GrpcClientWrapper):
196
199
 
197
200
  Args:
198
201
  file_id: The ID of the file to be retrieved
202
+ context: The context of the files (mission or setup)
199
203
  include_content: Whether to include file content in response
200
204
 
201
205
  Returns:
@@ -204,9 +208,14 @@ class GrpcFilesystem(FilesystemStrategy, GrpcClientWrapper):
204
208
  Raises:
205
209
  FilesystemServiceError: If there is an error retrieving the file
206
210
  """
211
+ match context:
212
+ case "setup":
213
+ context_id = self.setup_id
214
+ case "mission":
215
+ context_id = self.mission_id
207
216
  with GrpcFilesystem._handle_grpc_errors("GetFile"):
208
217
  request = filesystem_pb2.GetFileRequest(
209
- context=self.mission_id,
218
+ context=context_id,
210
219
  file_id=file_id,
211
220
  include_content=include_content,
212
221
  )
@@ -320,7 +329,7 @@ class GrpcFilesystem(FilesystemStrategy, GrpcClientWrapper):
320
329
  """
321
330
  with GrpcFilesystem._handle_grpc_errors("GetFiles"):
322
331
  request = filesystem_pb2.GetFilesRequest(
323
- context=self.mission_id,
332
+ context=filters.context,
324
333
  filters=self._filter_to_proto(filters),
325
334
  include_content=include_content,
326
335
  list_size=list_size,
@@ -111,12 +111,13 @@ class ServicesConfig(BaseModel):
111
111
  """
112
112
  return getattr(self, f"_config_{name}", {})
113
113
 
114
- def init_strategy(self, name: str, mission_id: str, setup_version_id: str) -> ServicesStrategy:
114
+ def init_strategy(self, name: str, mission_id: str, setup_id: str, setup_version_id: str) -> ServicesStrategy:
115
115
  """Initialize a specific strategy.
116
116
 
117
117
  Args:
118
118
  name: The name of the strategy to initialize
119
119
  mission_id: The ID of the mission this strategy is associated with
120
+ setup_id: The setup ID for the strategy
120
121
  setup_version_id: The setup version ID for the strategy
121
122
 
122
123
  Returns:
@@ -131,7 +132,7 @@ class ServicesConfig(BaseModel):
131
132
  raise ValueError(msg)
132
133
 
133
134
  # Instantiate the strategy with the mission ID, setup version ID, and configuration
134
- return strategy_type(mission_id, setup_version_id, **self.get_strategy_config(name) or {})
135
+ return strategy_type(mission_id, setup_id, setup_version_id, **self.get_strategy_config(name) or {})
135
136
 
136
137
  @property
137
138
  def storage(self) -> type[StorageStrategy]:
@@ -215,13 +215,14 @@ class DefaultStorage(StorageStrategy):
215
215
  def __init__(
216
216
  self,
217
217
  mission_id: str,
218
+ setup_id: str,
218
219
  setup_version_id: str,
219
220
  config: dict[str, type[BaseModel]],
220
221
  storage_file_path: str = "local_storage",
221
222
  **kwargs, # noqa: ANN003, ARG002
222
223
  ) -> None:
223
224
  """Initialize the storage."""
224
- super().__init__(mission_id=mission_id, setup_version_id=setup_version_id, config=config)
225
+ super().__init__(mission_id=mission_id, setup_id=setup_id, setup_version_id=setup_version_id, config=config)
225
226
  self.storage_file_path = f"{self.mission_id}_{storage_file_path}.json"
226
227
  self.storage_file = Path(self.storage_file_path)
227
228
  self.storage = self._load_from_file()
@@ -200,13 +200,14 @@ class GrpcStorage(StorageStrategy, GrpcClientWrapper):
200
200
  def __init__(
201
201
  self,
202
202
  mission_id: str,
203
+ setup_id: str,
203
204
  setup_version_id: str,
204
205
  config: dict[str, type[BaseModel]],
205
206
  client_config: ClientConfig,
206
207
  **kwargs, # noqa: ANN003, ARG002
207
208
  ) -> None:
208
209
  """Initialize the storage."""
209
- super().__init__(mission_id=mission_id, setup_version_id=setup_version_id, config=config)
210
+ super().__init__(mission_id=mission_id, setup_id=setup_id, setup_version_id=setup_version_id, config=config)
210
211
 
211
212
  channel = self._init_channel(client_config)
212
213
  self.stub = storage_service_pb2_grpc.StorageServiceStub(channel)
@@ -163,15 +163,22 @@ class StorageStrategy(BaseStrategy, ABC):
163
163
  True if the deletion was successful, False otherwise
164
164
  """
165
165
 
166
- def __init__(self, mission_id: str, setup_version_id: str, config: dict[str, type[BaseModel]]) -> None:
166
+ def __init__(
167
+ self,
168
+ mission_id: str,
169
+ setup_id: str,
170
+ setup_version_id: str,
171
+ config: dict[str, type[BaseModel]],
172
+ ) -> None:
167
173
  """Initialize the storage strategy.
168
174
 
169
175
  Args:
170
176
  mission_id: The ID of the mission this strategy is associated with
177
+ setup_id: The ID of the setup
171
178
  setup_version_id: The ID of the setup version
172
179
  config: A dictionary mapping names to Pydantic model classes
173
180
  """
174
- super().__init__(mission_id, setup_version_id)
181
+ super().__init__(mission_id, setup_id, setup_version_id)
175
182
  # Schema configuration mapping keys to model classes
176
183
  self.config: dict[str, type[BaseModel]] = config
177
184
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: digitalkin
3
- Version: 0.2.19
3
+ Version: 0.2.21
4
4
  Summary: SDK to build kin used in DigitalKin
5
5
  Author-email: "DigitalKin.ai" <contact@digitalkin.ai>
6
6
  License: Attribution-NonCommercial-ShareAlike 4.0 International
@@ -462,9 +462,9 @@ Requires-Dist: typos>=1.34.0; extra == "dev"
462
462
  Requires-Dist: ruff>=0.12.5; extra == "dev"
463
463
  Requires-Dist: mypy>=1.17.0; extra == "dev"
464
464
  Requires-Dist: pyright>=1.1.403; extra == "dev"
465
- Requires-Dist: pre-commit>=4.2.0; extra == "dev"
465
+ Requires-Dist: pre-commit>=4.3.0; extra == "dev"
466
466
  Requires-Dist: bump2version>=1.0.1; extra == "dev"
467
- Requires-Dist: build>=1.2.2; extra == "dev"
467
+ Requires-Dist: build>=1.3.0; extra == "dev"
468
468
  Requires-Dist: twine>=6.1.0; extra == "dev"
469
469
  Requires-Dist: cryptography>=45.0.5; extra == "dev"
470
470
  Provides-Extra: examples
@@ -7,13 +7,13 @@ base_server/mock/__init__.py,sha256=YZFT-F1l_TpvJYuIPX-7kTeE1CfOjhx9YmNRXVoi-jQ,
7
7
  base_server/mock/mock_pb2.py,sha256=sETakcS3PAAm4E-hTCV1jIVaQTPEAIoVVHupB8Z_k7Y,1843
8
8
  base_server/mock/mock_pb2_grpc.py,sha256=BbOT70H6q3laKgkHfOx1QdfmCS_HxCY4wCOX84YAdG4,3180
9
9
  digitalkin/__init__.py,sha256=7LLBAba0th-3SGqcpqFO-lopWdUkVLKzLZiMtB-mW3M,162
10
- digitalkin/__version__.py,sha256=LHM4OqKaSNrr7rgQKMrzY_uftKOe1OypsBhuoDm0K6Q,191
10
+ digitalkin/__version__.py,sha256=QJb8Hs9hhnpQm5I47kYxQurSZN7wI4xdHPE8xSSPwIA,191
11
11
  digitalkin/logger.py,sha256=cFbIAZHOFx3nddOssRNYLXyqUPzR4CgDR_c-5wmB-og,1685
12
12
  digitalkin/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
13
  digitalkin/grpc_servers/__init__.py,sha256=0cJBlwipSmFdXkyH3T0i6OJ1WpAtNsZgYX7JaSnkbtg,804
14
14
  digitalkin/grpc_servers/_base_server.py,sha256=NXnnZPjJqUDWoumhEbb7EOEWB7d8XYwpQs-l97NTe4k,18647
15
15
  digitalkin/grpc_servers/module_server.py,sha256=hOvoY2XFjxmgkbAsMex5a-m7OPyljnz0Gh9BJxtDtJo,10259
16
- digitalkin/grpc_servers/module_servicer.py,sha256=FSPcGHoEH_hfuGNDpNNN-bifSsxRnY-vOUBRZlE8hb4,18441
16
+ digitalkin/grpc_servers/module_servicer.py,sha256=dnEozIDwkAoinh3lIbT3ZJ9YANlghC3iiQKNYFNHmTI,18805
17
17
  digitalkin/grpc_servers/registry_server.py,sha256=StY18DKYoPKQIU1SIzgito6D4_QA1aMVddZ8O2WGlHY,2223
18
18
  digitalkin/grpc_servers/registry_servicer.py,sha256=dqsKGHZ0LnaIvGt4ipaAuigd37sbJBndT4MAT029GsY,16471
19
19
  digitalkin/grpc_servers/utils/exceptions.py,sha256=SyOgvjggaUECYmSiqy8KJLHwHVt5IClSTxslHM-IZzI,931
@@ -30,30 +30,30 @@ digitalkin/models/services/__init__.py,sha256=HsW7MUGFPvH7Ri28WN4BHHBfEQk5dzU_9F
30
30
  digitalkin/models/services/cost.py,sha256=QTEuFD6xz62nob0z4ksE-INJWcZ-iFiuNW5mvXhpFes,1599
31
31
  digitalkin/models/services/storage.py,sha256=cYTVIriGKiprF9OerhSxmc_jM6fUTVwmeon1yQCinkE,143
32
32
  digitalkin/modules/__init__.py,sha256=VwVbKok81NGyPIBZgEj_SR-59G8tTlSb4eBJI9W6Vx4,281
33
- digitalkin/modules/_base_module.py,sha256=JxiInXqnSltdiUjZMkRejURCBJ5MCBXz6SCsYKx-c0Q,16889
33
+ digitalkin/modules/_base_module.py,sha256=uZ19kOgF-z5iEXjtfp0mBlex5XnifvuxFc7_Gj810qE,17365
34
34
  digitalkin/modules/archetype_module.py,sha256=lOe3yYufwfylZR_VGy1w-zqdqVaMI_JANfKkbH9eODE,471
35
35
  digitalkin/modules/tool_module.py,sha256=h9oo2vrFJdiBLW2qL_m79a9hEzfXV0L6bZd4KD5OSQs,422
36
36
  digitalkin/modules/trigger_handler.py,sha256=TpxFc_xkYia9B9rAvHkj02e818W08JOfIqssQqbsCpM,1785
37
- digitalkin/modules/job_manager/base_job_manager.py,sha256=3t2OPqlsFGKsKAEUsPS0hPin1uZGmeSUhK8prWwaoKI,5987
37
+ digitalkin/modules/job_manager/base_job_manager.py,sha256=4qrCw68ZAleZp8vnpd1qYH320aQO0ApH2cZrKM87Grk,6121
38
38
  digitalkin/modules/job_manager/job_manager_models.py,sha256=onHy-DfInLZQveniMIWIKwTKSQjojz500JvHB54x93c,1129
39
- digitalkin/modules/job_manager/single_job_manager.py,sha256=Yx8uk97GPauZWnMOPtu3HAQ110-tsfOsMiTPosJ2AV8,10287
40
- digitalkin/modules/job_manager/taskiq_broker.py,sha256=rj7jXp-5K9345HY-ECueu5BPBFRP9Os_BiM-EgVjK_c,7304
41
- digitalkin/modules/job_manager/taskiq_job_manager.py,sha256=9rQBqmidamkblvZLzDdz8C1TVVIxQ-vZfxZXUR-NRw8,10038
39
+ digitalkin/modules/job_manager/single_job_manager.py,sha256=nMC1BX6G62Ehmam7aVSAlKW9ShJDB1ntuMq7KbD_sSI,10564
40
+ digitalkin/modules/job_manager/taskiq_broker.py,sha256=4q3U03SNlb1ypup0VOa73KHLKJ9peuZUPCR5eFLMNAk,7498
41
+ digitalkin/modules/job_manager/taskiq_job_manager.py,sha256=JxvNS95J_kEuRP08PvC3pQIO3DL8V1GRv7u6WUG59xg,10254
42
42
  digitalkin/services/__init__.py,sha256=LqGk_5DJy8Bzz62ajIq9jCeYNKQUIgtSCpafZk15FLc,910
43
- digitalkin/services/base_strategy.py,sha256=QAQnJw1BbqcYMSzwlFyhHP5juBH2WKrZzWxqDr_sDHI,638
44
- digitalkin/services/services_config.py,sha256=4hc7-rHgSigoS3SuV0V9FReD2Dz7XoMXcD6iMBP2KKM,7391
43
+ digitalkin/services/base_strategy.py,sha256=yA9KUJGRKuuaxA6l3GcMv8zKfWoIsW03UxJT80Yea2I,766
44
+ digitalkin/services/services_config.py,sha256=JnyzZcG7OYBelwgn-wdVgY2n3yFTEkwLPHRZB8Tjw10,7468
45
45
  digitalkin/services/services_models.py,sha256=5zXkWcfKnXGwQi9sN4OAL3XrgqOcmsTl8ai5Mi4RPsw,1668
46
46
  digitalkin/services/agent/__init__.py,sha256=vJc8JN0pdtA8ecypLBeHrwAUIW6H2C8NyW-dk24rTpk,244
47
47
  digitalkin/services/agent/agent_strategy.py,sha256=42Q9RciHX6tg3CgDQkbrlIx4h_TX0WIuSpLmCjitVmA,492
48
48
  digitalkin/services/agent/default_agent.py,sha256=4N_E_eQxJGOx1KVUUg5jNOje-3ncMxF3ePB-uDuGrJc,345
49
49
  digitalkin/services/cost/__init__.py,sha256=Wi9ZB4LSXFsUYgkX-V1UJQkVXYDNDpp8q2dXccR2uRM,303
50
- digitalkin/services/cost/cost_strategy.py,sha256=VhHeqi9WnE1yoDBBVp5qmqwIt5tTZHU6_Z_jld8CVeE,2535
51
- digitalkin/services/cost/default_cost.py,sha256=mEd0VL_tMcGU41q0f9MFeBYeKZBenv0mIHuwgXlQ7uQ,3869
52
- digitalkin/services/cost/grpc_cost.py,sha256=p_5mG72N7e4bxwBOD9DNokvLtinBILiqCfllmkqpmhw,6253
50
+ digitalkin/services/cost/cost_strategy.py,sha256=MpPX33P_S5b2by6F4zT-rcyeRuh2V4NYPZe05VpDOGQ,2649
51
+ digitalkin/services/cost/default_cost.py,sha256=XE7kNFde8NmbulU9m1lc3mi-vHFkbaJf0XHUc0D4UHE,3945
52
+ digitalkin/services/cost/grpc_cost.py,sha256=cGtb0atPXSEEOrNIWee-o3ScfNRSAFXJGDu0vcWT6zg,6295
53
53
  digitalkin/services/filesystem/__init__.py,sha256=BhwMl_BUvM0d65fmglkp0SVwn3RfYiUOKJgIMnOCaGM,381
54
- digitalkin/services/filesystem/default_filesystem.py,sha256=pQI7Sc9WNJrxxwd21iIviWcrKKuxuj3BdoAT-rghYGk,15023
55
- digitalkin/services/filesystem/filesystem_strategy.py,sha256=MlSgEDjjCy1GjtpdO_rKiF2NtfP4DpSU_VF2UtwQ7Ug,9130
56
- digitalkin/services/filesystem/grpc_filesystem.py,sha256=W-TYe76Zbf2KS0Z4NMVUjRdP6m2IzU7-VXiBdecOjpk,12489
54
+ digitalkin/services/filesystem/default_filesystem.py,sha256=qfC4jorfo86fBBnth-4ft13VuaGdvIHgWOVurCykXIk,15182
55
+ digitalkin/services/filesystem/filesystem_strategy.py,sha256=zibVLvX_IBQ-kgh-KYzHdszDeiHFPEAZszu_k99x1GQ,9487
56
+ digitalkin/services/filesystem/grpc_filesystem.py,sha256=0Wd4ZSEPznbATUABJwhn44wCWMX066Glm_FtLYSk16s,12819
57
57
  digitalkin/services/identity/__init__.py,sha256=InkeyLgFYYwItx8mePA8HpfacOMWZwwuc0G4pWtKq9s,270
58
58
  digitalkin/services/identity/default_identity.py,sha256=Y2auZHrGSZTIN5D8HyjLvLcNbYFM1CNUE23x7p5VIGw,386
59
59
  digitalkin/services/identity/identity_strategy.py,sha256=skappBbds1_qa0Gr24FGrNX1N0_OYhYT1Lh7dUaAirE,429
@@ -68,22 +68,22 @@ digitalkin/services/snapshot/__init__.py,sha256=Uzlnzo0CYlSpVsdiI37hW7xQk8hu3YA1
68
68
  digitalkin/services/snapshot/default_snapshot.py,sha256=Mb8QwWRsHh9I_tN0ln_ZiFa1QCZxOVWmuVLemQOTWpc,1058
69
69
  digitalkin/services/snapshot/snapshot_strategy.py,sha256=B1TU3V_k9A-OdqBkdyc41-ihnrW5Btcwd1KyQdHT46A,898
70
70
  digitalkin/services/storage/__init__.py,sha256=T-ocYLLphudkQgzvG47jBOm5GQsRFRIGA88y7Ur4akg,341
71
- digitalkin/services/storage/default_storage.py,sha256=m53sLOx0JVbua4weD_4GQBKK3c0UDf9HC5BnvSJAkvg,7933
72
- digitalkin/services/storage/grpc_storage.py,sha256=TWllxZGYse6f6AZp3YmYbskojOFTO1VkGSVde1zdQ6Q,7164
73
- digitalkin/services/storage/storage_strategy.py,sha256=vZWPk49AyJs7RwhSfUwDiTG1A7C4Ccd8y-VVPslyf0w,8734
71
+ digitalkin/services/storage/default_storage.py,sha256=D8e-UYUkb2GvDEHMWcN3EkcIKXWA8DrsaQsXVjoXAYQ,7975
72
+ digitalkin/services/storage/grpc_storage.py,sha256=3ZHGq3wnbF8dZjJcYN1EGTEUXA4rur0XEGtTBwAcGB4,7206
73
+ digitalkin/services/storage/storage_strategy.py,sha256=sERF5tIJnzpb1iNqTXic9xRkGaXMifo6kb709ubB-Yo,8848
74
74
  digitalkin/utils/__init__.py,sha256=sJnY-ZUgsjMfojAjONC1VN14mhgIDnzyOlGkw21rRnM,28
75
75
  digitalkin/utils/arg_parser.py,sha256=nvjI1pKDY1HfS0oGcMQPtdTQcggXLtpxXMbnMxNEKRU,3109
76
76
  digitalkin/utils/development_mode_action.py,sha256=TqRuAF_A7bDD4twRB4PnZcRoNeaiAnEdxM5kvy4aoaA,1511
77
77
  digitalkin/utils/llm_ready_schema.py,sha256=JjMug_lrQllqFoanaC091VgOqwAd-_YzcpqFlS7p778,2375
78
78
  digitalkin/utils/package_discover.py,sha256=3e9-6Vf3yAAv2VkHHVK4QVqHJBxQqg3d8uuDTsXph24,13471
79
- digitalkin-0.2.19.dist-info/licenses/LICENSE,sha256=Ies4HFv2r2hzDRakJYxk3Y60uDFLiG-orIgeTpstnIo,20327
79
+ digitalkin-0.2.21.dist-info/licenses/LICENSE,sha256=Ies4HFv2r2hzDRakJYxk3Y60uDFLiG-orIgeTpstnIo,20327
80
80
  modules/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
81
81
  modules/cpu_intensive_module.py,sha256=ejB9XPnFfA0uCuFUQbM3fy5UYfqqAlF36rv_P5Ri8ho,8363
82
82
  modules/minimal_llm_module.py,sha256=Ijld__ZnhzfLwpXD1XVkLZ7jyKZKyOFZczOpiPttJZc,11216
83
83
  modules/text_transform_module.py,sha256=bwPSnEUthZQyfLwcTLo52iAxItAoknkLh8Y3m5aywaY,7251
84
84
  services/filesystem_module.py,sha256=71Mcja8jCQqiqFHPdsIXplFIHTvgkxRhp0TRXuCfgkk,7430
85
85
  services/storage_module.py,sha256=ybTMqmvGaTrR8PqJ4FU0cwxaDjT36TskVrGoetTGmno,6955
86
- digitalkin-0.2.19.dist-info/METADATA,sha256=9gzMCkxF-zbZRMDfEzcTTrD6kde7pgMdyM-ckPv_wR0,30579
87
- digitalkin-0.2.19.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
88
- digitalkin-0.2.19.dist-info/top_level.txt,sha256=gcjqlyrZuLjIyxrOIavCQM_olpr6ND5kPKkZd2j0xGo,40
89
- digitalkin-0.2.19.dist-info/RECORD,,
86
+ digitalkin-0.2.21.dist-info/METADATA,sha256=JdeHnI5TK-xDUJe-Im3Zd8ILrSAkjlkljBCBz-2_ilA,30579
87
+ digitalkin-0.2.21.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
88
+ digitalkin-0.2.21.dist-info/top_level.txt,sha256=gcjqlyrZuLjIyxrOIavCQM_olpr6ND5kPKkZd2j0xGo,40
89
+ digitalkin-0.2.21.dist-info/RECORD,,