synapse-sdk 1.0.0a7__py3-none-any.whl → 1.0.0a9__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 synapse-sdk might be problematic. Click here for more details.

Files changed (34) hide show
  1. synapse_sdk/clients/agent/__init__.py +9 -7
  2. synapse_sdk/clients/agent/core.py +7 -0
  3. synapse_sdk/clients/backend/__init__.py +5 -4
  4. synapse_sdk/clients/backend/annotation.py +10 -10
  5. synapse_sdk/clients/backend/dataset.py +5 -5
  6. synapse_sdk/clients/backend/integration.py +12 -0
  7. synapse_sdk/clients/backend/ml.py +8 -7
  8. synapse_sdk/clients/base.py +10 -1
  9. synapse_sdk/loggers.py +74 -12
  10. synapse_sdk/plugins/categories/base.py +35 -5
  11. synapse_sdk/plugins/categories/neural_net/actions/train.py +108 -7
  12. synapse_sdk/plugins/categories/neural_net/templates/plugin/train.py +1 -1
  13. synapse_sdk/plugins/categories/smart_tool/__init__.py +0 -0
  14. synapse_sdk/plugins/categories/smart_tool/actions/__init__.py +0 -0
  15. synapse_sdk/plugins/categories/smart_tool/actions/auto_label.py +22 -0
  16. synapse_sdk/plugins/categories/smart_tool/templates/config.yaml +6 -0
  17. synapse_sdk/plugins/categories/smart_tool/templates/plugin/__init__.py +0 -0
  18. synapse_sdk/plugins/categories/smart_tool/templates/plugin/auto_label.py +11 -0
  19. synapse_sdk/plugins/exceptions.py +6 -0
  20. synapse_sdk/plugins/models.py +10 -3
  21. synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/.env +24 -0
  22. synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/config.yaml +0 -12
  23. synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/main.py +4 -0
  24. synapse_sdk/utils/file.py +15 -0
  25. synapse_sdk/utils/pydantic/__init__.py +0 -0
  26. synapse_sdk/utils/pydantic/config.py +4 -0
  27. synapse_sdk/utils/pydantic/errors.py +33 -0
  28. synapse_sdk/utils/pydantic/validators.py +7 -0
  29. {synapse_sdk-1.0.0a7.dist-info → synapse_sdk-1.0.0a9.dist-info}/METADATA +1 -1
  30. {synapse_sdk-1.0.0a7.dist-info → synapse_sdk-1.0.0a9.dist-info}/RECORD +34 -20
  31. {synapse_sdk-1.0.0a7.dist-info → synapse_sdk-1.0.0a9.dist-info}/LICENSE +0 -0
  32. {synapse_sdk-1.0.0a7.dist-info → synapse_sdk-1.0.0a9.dist-info}/WHEEL +0 -0
  33. {synapse_sdk-1.0.0a7.dist-info → synapse_sdk-1.0.0a9.dist-info}/entry_points.txt +0 -0
  34. {synapse_sdk-1.0.0a7.dist-info → synapse_sdk-1.0.0a9.dist-info}/top_level.txt +0 -0
@@ -1,21 +1,23 @@
1
+ from synapse_sdk.clients.agent.core import CoreClientMixin
1
2
  from synapse_sdk.clients.agent.service import ServiceClientMixin
2
3
 
3
4
 
4
- class AgentClient(ServiceClientMixin):
5
+ class AgentClient(CoreClientMixin, ServiceClientMixin):
5
6
  name = 'Agent'
6
7
  agent_token = None
7
8
  user_token = None
8
9
  tenant = None
9
10
 
10
- def __init__(self, base_url, agent_token, user_token, tenant):
11
+ def __init__(self, base_url, agent_token, user_token=None, tenant=None):
11
12
  super().__init__(base_url)
12
13
  self.agent_token = agent_token
13
14
  self.user_token = user_token
14
15
  self.tenant = tenant
15
16
 
16
17
  def _get_headers(self):
17
- return {
18
- 'Authorization': self.agent_token,
19
- 'SYNAPSE-User': f'Token {self.user_token}',
20
- 'SYNAPSE-Tenant': f'Token {self.tenant}',
21
- }
18
+ headers = {'Authorization': self.agent_token}
19
+ if self.user_token:
20
+ headers['SYNAPSE-User'] = f'Token {self.user_token}'
21
+ if self.tenant:
22
+ headers['SYNAPSE-Tenant'] = f'Token {self.tenant}'
23
+ return headers
@@ -0,0 +1,7 @@
1
+ from synapse_sdk.clients.base import BaseClient
2
+
3
+
4
+ class CoreClientMixin(BaseClient):
5
+ def health_check(self):
6
+ path = 'health/'
7
+ return self._get(path)
@@ -9,14 +9,15 @@ class BackendClient(AnnotationClientMixin, DatasetClientMixin, IntegrationClient
9
9
  token = None
10
10
  tenant = None
11
11
 
12
- def __init__(self, base_url, token, tenant=None):
12
+ def __init__(self, base_url, token=None, tenant=None):
13
13
  super().__init__(base_url)
14
14
  self.token = token
15
- if tenant:
16
- self.tenant = tenant
15
+ self.tenant = tenant
17
16
 
18
17
  def _get_headers(self):
19
- headers = {'Authorization': f'Token {self.token}'}
18
+ headers = {}
19
+ if self.token:
20
+ headers = {'Authorization': f'Token {self.token}'}
20
21
  if self.tenant:
21
22
  headers['SYNAPSE-Tenant'] = f'Token {self.tenant}'
22
23
  return headers
@@ -7,23 +7,23 @@ class AnnotationClientMixin(BaseClient):
7
7
  path = f'projects/{pk}/'
8
8
  return self._get(path)
9
9
 
10
- def get_label_tag(self, pk):
11
- path = f'label_tags/{pk}/'
10
+ def get_task_tag(self, pk):
11
+ path = f'task_tags/{pk}/'
12
12
  return self._get(path)
13
13
 
14
- def list_label_tags(self, data):
15
- path = 'label_tags/'
14
+ def list_task_tags(self, data):
15
+ path = 'task_tags/'
16
16
  return self._list(path, data=data)
17
17
 
18
- def list_labels(self, data, url_conversion=None, list_all=False):
19
- path = 'labels/'
18
+ def list_tasks(self, data, url_conversion=None, list_all=False):
19
+ path = 'tasks/'
20
20
  url_conversion = get_default_url_conversion(url_conversion, files_fields=['files'])
21
21
  return self._list(path, data=data, url_conversion=url_conversion, list_all=list_all)
22
22
 
23
- def create_labels(self, data):
24
- path = 'labels/'
23
+ def create_tasks(self, data):
24
+ path = 'tasks/'
25
25
  return self._post(path, data=data)
26
26
 
27
- def set_tags_labels(self, data, params=None):
28
- path = 'labels/set_tags/'
27
+ def set_tags_tasks(self, data, params=None):
28
+ path = 'tasks/set_tags/'
29
29
  return self._post(path, data=data, params=params)
@@ -33,15 +33,15 @@ class DatasetClientMixin(BaseClient):
33
33
  data_units = self.create_data_units(batch)
34
34
 
35
35
  if project_id:
36
- labels_data = []
36
+ tasks_data = []
37
37
  for data, data_unit in zip(batch, data_units):
38
- label_data = {'project': project_id, 'data_unit': data_unit['id']}
38
+ task_data = {'project': project_id, 'data_unit': data_unit['id']}
39
39
  if 'ground_truth' in data:
40
- label_data['ground_truth'] = data['ground_truth']
40
+ task_data['ground_truth'] = data['ground_truth']
41
41
 
42
- labels_data.append(label_data)
42
+ tasks_data.append(task_data)
43
43
 
44
- self.create_labels(labels_data)
44
+ self.create_tasks(tasks_data)
45
45
 
46
46
  def import_data_file(self, data, dataset_id):
47
47
  for name, path in data['files'].items():
@@ -2,6 +2,10 @@ from synapse_sdk.clients.base import BaseClient
2
2
 
3
3
 
4
4
  class IntegrationClientMixin(BaseClient):
5
+ def health_check_agent(self, token):
6
+ path = f'agents/{token}/connect/'
7
+ return self._post(path)
8
+
5
9
  def get_plugin(self, pk):
6
10
  path = f'plugins/{pk}/'
7
11
  return self._get(path)
@@ -27,6 +31,14 @@ class IntegrationClientMixin(BaseClient):
27
31
  files = {'file': data.pop('file')}
28
32
  return self._post(path, data=data, files=files)
29
33
 
34
+ def list_jobs(self, params=None):
35
+ path = 'jobs/'
36
+ return self._get(path, params=params)
37
+
38
+ def update_job(self, pk, data):
39
+ path = f'jobs/{pk}/'
40
+ return self._patch(path, data=data)
41
+
30
42
  def list_job_console_logs(self, pk):
31
43
  path = f'jobs/{pk}/console_logs/'
32
44
  return self._get(path)
@@ -3,6 +3,10 @@ from synapse_sdk.clients.utils import get_default_url_conversion
3
3
 
4
4
 
5
5
  class MLClientMixin(BaseClient):
6
+ def list_models(self, params=None):
7
+ path = 'models/'
8
+ return self._get(path, params=params)
9
+
6
10
  def get_model(self, pk, params=None, url_conversion=None):
7
11
  path = f'models/{pk}/'
8
12
  url_conversion = get_default_url_conversion(
@@ -12,13 +16,10 @@ class MLClientMixin(BaseClient):
12
16
 
13
17
  def create_model(self, data):
14
18
  path = 'models/'
15
- return self._post(path, data=data)
16
-
17
- def update_model(self, pk, data, files=None):
18
- path = f'models/{pk}/'
19
- return self._patch(path, data=data, files=files)
19
+ files = {'file': data.pop('file')}
20
+ return self._post(path, data=data, files=files)
20
21
 
21
- def list_train_dataset(self, params=None, url_conversion=None, list_all=False):
22
- path = 'train_dataset/'
22
+ def list_ground_truth_events(self, params=None, url_conversion=None, list_all=False):
23
+ path = 'ground_truth_events/'
23
24
  url_conversion = get_default_url_conversion(url_conversion, files_fields=['files'])
24
25
  return self._list(path, params=params, url_conversion=url_conversion, list_all=list_all)
@@ -33,6 +33,9 @@ class BaseClient:
33
33
  if kwargs.get('files') is not None:
34
34
  for name, file in kwargs['files'].items():
35
35
  kwargs['files'][name] = Path(str(file)).open(mode='rb')
36
+ for name, value in kwargs['data'].items():
37
+ if isinstance(value, dict):
38
+ kwargs['data'][name] = json.dumps(value)
36
39
  else:
37
40
  headers['Content-Type'] = 'application/json'
38
41
  if 'data' in kwargs:
@@ -47,7 +50,10 @@ class BaseClient:
47
50
  except requests.ConnectionError:
48
51
  raise ClientError(408, f'{self.name} is not responding')
49
52
 
50
- return response.json()
53
+ try:
54
+ return response.json()
55
+ except ValueError:
56
+ return response.text
51
57
 
52
58
  def _get(self, path, url_conversion=None, **kwargs):
53
59
  response = self._request('get', path, **kwargs)
@@ -79,3 +85,6 @@ class BaseClient:
79
85
  yield from response['results']
80
86
  if response['next']:
81
87
  yield from self._list_all(response['next'], url_conversion, **kwargs)
88
+
89
+ def exists(self, api, *args, **kwargs):
90
+ return getattr(self, api)(*args, **kwargs)['count'] > 0
synapse_sdk/loggers.py CHANGED
@@ -1,24 +1,78 @@
1
1
  import datetime
2
+ import time
2
3
 
3
4
  from synapse_sdk.clients.exceptions import ClientError
4
5
 
5
6
 
6
7
  class BaseLogger:
7
- progress_records = {}
8
+ progress_record = {}
9
+ progress_categories = None
10
+ current_category = None
11
+ time_begin_per_category = {}
8
12
 
9
- def set_progress(self, current, total, category=''):
10
- percent = 0
11
- if total > 0:
12
- percent = (current / total) * 100
13
- percent = float(round(percent, 2))
13
+ def __init__(self, progress_categories=None):
14
+ self.progress_categories = progress_categories
15
+ if progress_categories:
16
+ self.progress_record['categories'] = progress_categories
14
17
 
15
- self.progress_records[category] = {'current': current, 'total': total, 'percent': percent}
18
+ def set_progress(self, current, total, category=None):
19
+ assert 0 <= current <= total and total > 0
20
+ assert category is not None or 'categories' not in self.progress_record
21
+
22
+ percent = (current / total) * 100
23
+ percent = round(percent, 2)
24
+ # TODO current 0 으로 시작하지 않아도 작동되도록 수정
25
+ if current == 0:
26
+ self.time_begin_per_category[category] = time.time()
27
+ time_remaining = None
28
+ else:
29
+ seconds_per_item = (time.time() - self.time_begin_per_category[category]) / current
30
+ time_remaining = round(seconds_per_item * (total - current), 2)
31
+
32
+ current_progress = {'percent': percent, 'time_remaining': time_remaining}
33
+
34
+ if category:
35
+ self.current_category = category
36
+ self.progress_record['categories'][category].update(current_progress)
37
+ else:
38
+ self.progress_record.update(current_progress)
39
+
40
+ def get_current_progress(self):
41
+ categories = self.progress_record.get('categories')
42
+
43
+ if categories:
44
+ category_progress = None
45
+
46
+ overall = 0
47
+ for category, category_record in categories.items():
48
+ if category == self.current_category:
49
+ break
50
+ overall += category_record['proportion']
51
+
52
+ category_record = categories[self.current_category]
53
+ category_percent = category_record.get('percent', 0)
54
+ if not category_progress and 'percent' in category_record:
55
+ category_progress = {
56
+ 'category': self.current_category,
57
+ 'percent': category_percent,
58
+ 'time_remaining': category_record.get('time_remaining'),
59
+ }
60
+ if category_percent > 0:
61
+ overall += round(category_record['proportion'] / 100 * category_percent, 2)
62
+ progress = {'overall': overall, **category_progress}
63
+ else:
64
+ progress = {
65
+ 'overall': self.progress_record.get('percent'),
66
+ 'time_remaining': self.progress_record.get('time_remaining'),
67
+ }
68
+
69
+ return progress
16
70
 
17
71
 
18
72
  class ConsoleLogger(BaseLogger):
19
- def set_progress(self, current, total, category=''):
73
+ def set_progress(self, current, total, category=None):
20
74
  super().set_progress(current, total, category=category)
21
- print(self.progress_records)
75
+ print(self.get_current_progress())
22
76
 
23
77
  def log(self, action, data):
24
78
  print(action, data)
@@ -29,13 +83,21 @@ class BackendLogger(BaseLogger):
29
83
  client = None
30
84
  job_id = None
31
85
 
32
- def __init__(self, client, job_id):
86
+ def __init__(self, client, job_id, **kwargs):
87
+ super().__init__(**kwargs)
33
88
  self.client = client
34
89
  self.job_id = job_id
35
90
 
36
- def set_progress(self, current, total, category=''):
91
+ def set_progress(self, current, total, category=None):
37
92
  super().set_progress(current, total, category=category)
38
- # TODO set_progress to the job
93
+ try:
94
+ progress_record = {
95
+ 'record': self.progress_record,
96
+ 'current_progress': self.get_current_progress(),
97
+ }
98
+ self.client.update_job(self.job_id, data={'progress_record': progress_record})
99
+ except ClientError:
100
+ pass
39
101
 
40
102
  def log(self, action, data):
41
103
  print(action, data)
@@ -6,25 +6,35 @@ from pprint import pprint
6
6
 
7
7
  import ray
8
8
  import requests
9
+ from pydantic import ValidationError
9
10
  from ray.dashboard.modules.job.sdk import JobSubmissionClient
10
11
 
11
12
  from synapse_sdk.plugins.enums import RunMethod
13
+ from synapse_sdk.plugins.exceptions import ActionValidationError
12
14
  from synapse_sdk.plugins.models import PluginRelease, Run
13
15
  from synapse_sdk.plugins.upload import archive_and_upload, build_and_upload, download_and_upload
14
16
  from synapse_sdk.utils.module_loading import import_string
17
+ from synapse_sdk.utils.pydantic.errors import pydantic_to_drf_error
15
18
 
16
19
 
17
20
  class Action:
21
+ # class 변수
18
22
  name = None
19
23
  category = None
20
24
  method = None
25
+ run_class = Run
26
+ params_model = None
27
+ progress_categories = None
28
+
29
+ # init 변수
21
30
  params = None
22
31
  plugin_config = None
23
32
  plugin_release = None
24
33
  config = None
25
- client = None
26
- debug = False
27
- run_class = Run
34
+ job_id = None
35
+ direct = None
36
+ debug = None
37
+ envs = None
28
38
  run = None
29
39
 
30
40
  default_envs = [
@@ -57,10 +67,14 @@ class Action:
57
67
  def plugin_storage_url(self):
58
68
  return self.envs['SYNAPSE_PLUGIN_STORAGE']
59
69
 
70
+ @property
71
+ def client(self):
72
+ return self.run.client
73
+
60
74
  @property
61
75
  def plugin_url(self):
62
76
  if self.debug:
63
- plugin_path = self.envs.get('SYNAPSE_DEBUG_PLUGIN_PATH', '.')
77
+ plugin_path = self.envs.get('SYNAPSE_DEBUG_PLUGIN_PATH') or '.'
64
78
  if plugin_path.startswith('https://'): # TODO ray에서 지원하는 remote uri 형식 (https, s3, gs) 모두 지원
65
79
  plugin_url = plugin_path
66
80
  elif plugin_path.startswith('http://'):
@@ -85,6 +99,8 @@ class Action:
85
99
 
86
100
  def get_run(self):
87
101
  context = {
102
+ 'plugin_release': self.plugin_release,
103
+ 'progress_categories': self.progress_categories,
88
104
  'params': self.params,
89
105
  'envs': self.envs,
90
106
  'debug': self.debug,
@@ -110,12 +126,22 @@ class Action:
110
126
  pprint(runtime_env)
111
127
  return runtime_env
112
128
 
129
+ def validate_params(self):
130
+ if self.params_model:
131
+ try:
132
+ self.params_model.model_validate(self.params, context={'action': self})
133
+ except ValidationError as e:
134
+ raise ActionValidationError({'params': pydantic_to_drf_error(e)})
135
+
113
136
  def run_action(self):
137
+ self.validate_params()
114
138
  if self.direct:
115
139
  if self.method == RunMethod.RESTAPI:
116
140
  return self.start_by_restapi()
117
141
  else:
118
- return self.start()
142
+ result = self.start()
143
+ self.post_action_by_job(result)
144
+ return result
119
145
  return getattr(self, f'start_by_{self.method.value}')()
120
146
 
121
147
  def start(self):
@@ -175,3 +201,7 @@ class Action:
175
201
  response = getattr(requests, method)(url, **self.params)
176
202
  # TODO ok response가 아닌 경우 대응하기
177
203
  return response.json()
204
+
205
+ def post_action_by_job(self, result):
206
+ if self.client:
207
+ self.client.update_job(self.job_id, data={'result': result})
@@ -1,15 +1,60 @@
1
+ import copy
2
+ import tempfile
3
+ from decimal import Decimal
4
+ from pathlib import Path
5
+ from typing import Annotated
6
+
7
+ from pydantic import AfterValidator, BaseModel, field_validator
8
+ from pydantic_core import PydanticCustomError
9
+
10
+ from synapse_sdk.clients.exceptions import ClientError
1
11
  from synapse_sdk.plugins.categories.base import Action
2
12
  from synapse_sdk.plugins.categories.decorators import register_action
3
13
  from synapse_sdk.plugins.enums import PluginCategory, RunMethod
4
14
  from synapse_sdk.plugins.models import Run
15
+ from synapse_sdk.utils.file import archive
16
+ from synapse_sdk.utils.pydantic.validators import non_blank
5
17
 
6
18
 
7
19
  class TrainRun(Run):
8
20
  def log_metric(self, x, i, **kwargs):
9
21
  self.log(x, {x: i, **kwargs})
10
22
 
11
- def log_model(self, files, status=None):
12
- pass
23
+
24
+ class Hyperparameter(BaseModel):
25
+ batch_size: int
26
+ iterations: int
27
+ learning_rate: Decimal
28
+
29
+
30
+ class TrainParams(BaseModel):
31
+ name: Annotated[str, AfterValidator(non_blank)]
32
+ description: str
33
+ checkpoint: int | None
34
+ dataset: int
35
+ hyperparameter: Hyperparameter
36
+
37
+ @field_validator('name')
38
+ @staticmethod
39
+ def unique_name(value, info):
40
+ action = info.context['action']
41
+ client = action.client
42
+ try:
43
+ model_exists = client.exists('list_models', params={'name': value})
44
+ job_exists = client.exists(
45
+ 'list_jobs',
46
+ params={
47
+ 'ids_ex': action.job_id,
48
+ 'category': 'neural_net',
49
+ 'action': 'train',
50
+ 'is_active': True,
51
+ 'params': f'name:{value}',
52
+ },
53
+ )
54
+ assert not model_exists and not job_exists, '존재하는 학습 이름입니다.'
55
+ except ClientError:
56
+ raise PydanticCustomError('client_error', '')
57
+ return value
13
58
 
14
59
 
15
60
  @register_action
@@ -18,9 +63,18 @@ class TrainAction(Action):
18
63
  category = PluginCategory.NEURAL_NET
19
64
  method = RunMethod.JOB
20
65
  run_class = TrainRun
21
-
22
- def get_dataset(self):
23
- return {}
66
+ params_model = TrainParams
67
+ progress_categories = {
68
+ 'dataset': {
69
+ 'proportion': 20,
70
+ },
71
+ 'train': {
72
+ 'proportion': 75,
73
+ },
74
+ 'model_upload': {
75
+ 'proportion': 5,
76
+ },
77
+ }
24
78
 
25
79
  def start(self):
26
80
  hyperparameter = self.params['hyperparameter']
@@ -32,10 +86,57 @@ class TrainAction(Action):
32
86
  # train dataset
33
87
  self.run.log_event('Starting model training.')
34
88
 
35
- model_files = self.entrypoint(self.run, input_dataset, hyperparameter)
89
+ result = self.entrypoint(self.run, input_dataset, hyperparameter)
36
90
 
37
91
  # upload model_data
38
92
  self.run.log_event('Registering model data.')
93
+ self.run.set_progress(0, 1, category='model_upload')
94
+ model = self.create_model(result)
95
+ self.run.set_progress(1, 1, category='model_upload')
39
96
 
40
97
  self.run.end_log()
41
- return model_files
98
+ return {'model_id': model['id']}
99
+
100
+ def get_dataset(self):
101
+ client = self.run.client
102
+ assert bool(client)
103
+
104
+ input_dataset = {}
105
+
106
+ ground_truths, count_dataset = client.list_ground_truth_events(
107
+ params={
108
+ 'fields': ['category', 'files', 'data'],
109
+ 'ground_truth_dataset_versions': self.params['dataset'],
110
+ },
111
+ list_all=True,
112
+ )
113
+ self.run.set_progress(0, count_dataset, category='dataset')
114
+ for i, ground_truth in enumerate(ground_truths, start=1):
115
+ self.run.set_progress(i, count_dataset, category='dataset')
116
+ try:
117
+ input_dataset[ground_truth['category']].append(ground_truth)
118
+ except KeyError:
119
+ input_dataset[ground_truth['category']] = [ground_truth]
120
+
121
+ return input_dataset
122
+
123
+ def create_model(self, path):
124
+ if not self.client:
125
+ print(path)
126
+
127
+ params = copy.deepcopy(self.params)
128
+ configuration_fields = ['hyperparameter']
129
+ configuration = {field: params.pop(field) for field in configuration_fields}
130
+
131
+ with tempfile.TemporaryDirectory() as temp_path:
132
+ input_path = Path(path)
133
+ archive_path = Path(temp_path, 'archive.zip')
134
+ archive(input_path, archive_path)
135
+
136
+ return self.client.create_model({
137
+ 'plugin': self.plugin_release.plugin,
138
+ 'version': self.plugin_release.version,
139
+ 'file': str(archive_path),
140
+ 'configuration': configuration,
141
+ **params,
142
+ })
@@ -9,6 +9,6 @@ def train(run, dataset, hyperparameter, checkpoint=None):
9
9
  loss = float(round((count_iterations - i) / count_iterations, 2))
10
10
  miou = 1 - loss
11
11
  run.log_metric('iteration', i, loss=loss, miou=miou)
12
- run.set_progress(i, count_iterations, category='iteration')
12
+ run.set_progress(i, count_iterations, category='train')
13
13
 
14
14
  return {'weight': '/tmp/agent/test/a.txt', 'config': '/tmp/agent/test/b.txt'}
File without changes
@@ -0,0 +1,22 @@
1
+ from synapse_sdk.plugins.categories.base import Action
2
+ from synapse_sdk.plugins.categories.decorators import register_action
3
+ from synapse_sdk.plugins.enums import PluginCategory, RunMethod
4
+
5
+
6
+ @register_action
7
+ class AutoLabelAction(Action):
8
+ name = 'label'
9
+ category = PluginCategory.SMART_TOOL
10
+ method = RunMethod.TASK
11
+
12
+ def get_auto_label(self):
13
+ return self.entrypoint(**self.params)
14
+
15
+ def run_model(self, input_data):
16
+ return {}
17
+
18
+ def start(self):
19
+ auto_label = self.get_auto_label()
20
+ input_data = auto_label.handle_input(self.params['input_data'])
21
+ output_data = self.run_model(input_data)
22
+ return auto_label.handle_output(output_data)
@@ -0,0 +1,6 @@
1
+ actions:
2
+ auto_label:
3
+ category: interactive_segmentation
4
+ entrypoint: plugin.auto_label.MyAutoLabel
5
+ model:
6
+ neural_nets: [sam2]
@@ -0,0 +1,11 @@
1
+ class MyAutoLabel:
2
+ def __init__(self, **kwargs):
3
+ pass
4
+
5
+ def handle_input(self, input_data):
6
+ """smart tool의 input을 model의 input 형태로 변환"""
7
+ return
8
+
9
+ def handle_output(self, output_data):
10
+ """model의 output을 smart tool의 output 형태로 변환"""
11
+ return
@@ -0,0 +1,6 @@
1
+ class ActionValidationError(Exception):
2
+ errors = None
3
+
4
+ def __init__(self, errors, *args):
5
+ self.errors = errors
6
+ super().__init__(errors, *args)
@@ -11,12 +11,14 @@ from synapse_sdk.utils.string import hash_text
11
11
 
12
12
  class PluginRelease:
13
13
  config: Dict[str, Any]
14
+ envs = None
14
15
 
15
- def __init__(self, config=None, plugin_path=None):
16
+ def __init__(self, config=None, plugin_path=None, envs=None):
16
17
  if config:
17
18
  self.config = config
18
19
  else:
19
20
  self.config = read_plugin_config(plugin_path=plugin_path)
21
+ self.envs = envs
20
22
 
21
23
  @cached_property
22
24
  def plugin(self):
@@ -72,15 +74,20 @@ class Run:
72
74
  self.set_logger()
73
75
 
74
76
  def set_logger(self):
77
+ kwargs = {'progress_categories': self.context['progress_categories']}
75
78
  if self.job_id:
76
79
  client = BackendClient(
77
80
  self.context['envs']['SYNAPSE_PLUGIN_RUN_HOST'],
78
81
  self.context['envs']['SYNAPSE_PLUGIN_RUN_USER_TOKEN'],
79
82
  self.context['envs']['SYNAPSE_PLUGIN_RUN_TENANT'],
80
83
  )
81
- self.logger = BackendLogger(client, self.job_id)
84
+ self.logger = BackendLogger(client, self.job_id, **kwargs)
82
85
  else:
83
- self.logger = ConsoleLogger()
86
+ self.logger = ConsoleLogger(**kwargs)
87
+
88
+ @property
89
+ def client(self):
90
+ return getattr(self.logger, 'client', None)
84
91
 
85
92
  def set_progress(self, current, total, category=''):
86
93
  self.logger.set_progress(current, total, category)
@@ -0,0 +1,24 @@
1
+ # RAY 관련
2
+ RAY_ADDRESS=
3
+ RAY_DASHBOARD_ADDRESS=
4
+ RAY_SERVE_ADDRESS=
5
+
6
+ # 플러그인 관련
7
+ SYNAPSE_PLUGIN_STORAGE=
8
+
9
+ # 플러그인 디버그 관련
10
+ SYNAPSE_DEBUG_PLUGIN_PATH=
11
+ SYNAPSE_DEBUG_MODULES=
12
+
13
+ # 플러그인 PUBLISH 관련
14
+ SYNAPSE_PLUGIN_PUBLISH_HOST=https://synapse.datamaker.io
15
+ SYNAPSE_PLUGIN_PUBLISH_USER_TOKEN=
16
+ SYNAPSE_PLUGIN_PUBLISH_TENANT=
17
+
18
+ # 플러그인 RUN 관련
19
+ SYNAPSE_PLUGIN_RUN_HOST=
20
+ SYNAPSE_PLUGIN_RUN_AGENT=
21
+ SYNAPSE_PLUGIN_RUN_AGENT_HOST=
22
+ SYNAPSE_PLUGIN_RUN_AGENT_TOKEN=
23
+ SYNAPSE_PLUGIN_RUN_USER_TOKEN=
24
+ SYNAPSE_PLUGIN_RUN_TENANT=
@@ -4,15 +4,3 @@ version: {{ cookiecutter.version }}
4
4
  readme: README.md
5
5
  description: {{ cookiecutter.description }}
6
6
  category: {{ cookiecutter.category }}
7
- actions:
8
- train:
9
- dataset: datamaker
10
- entrypoint: plugin.train.train
11
- deployment:
12
- entrypoint: plugin.inference.MockNetInference
13
- inference:
14
- method: restapi
15
- endpoints:
16
- - method: get
17
- test:
18
- entrypoint: plugin.test.test
@@ -0,0 +1,4 @@
1
+ from synapse_sdk import plugins
2
+
3
+ if __name__ == '__main__':
4
+ plugins.init()
synapse_sdk/utils/file.py CHANGED
@@ -1,6 +1,7 @@
1
1
  import hashlib
2
2
  import json
3
3
  import operator
4
+ import zipfile
4
5
  from functools import reduce
5
6
  from pathlib import Path
6
7
 
@@ -68,3 +69,17 @@ def calculate_checksum(file_path, prefix=''):
68
69
  if prefix:
69
70
  return f'dev-{checksum}'
70
71
  return checksum
72
+
73
+
74
+ def archive(input_path, output_path):
75
+ input_path = Path(input_path)
76
+ output_path = Path(output_path)
77
+
78
+ with zipfile.ZipFile(output_path, mode='w', compression=zipfile.ZIP_DEFLATED) as zipf:
79
+ if input_path.is_file():
80
+ zipf.write(input_path, input_path.name)
81
+ else:
82
+ for file_path in input_path.rglob('*'):
83
+ if file_path.is_file(): # Only add files, skip directories
84
+ arcname = file_path.relative_to(input_path.parent)
85
+ zipf.write(file_path, arcname)
File without changes
@@ -0,0 +1,4 @@
1
+ ERROR_MESSAGES = {
2
+ 'missing': '필수 값을 확인해주세요.',
3
+ 'blank': '필수 값을 확인해주세요.',
4
+ }
@@ -0,0 +1,33 @@
1
+ from typing import Any, Dict
2
+
3
+ from synapse_sdk.utils.pydantic.config import ERROR_MESSAGES
4
+
5
+
6
+ def pydantic_to_drf_error(e):
7
+ """
8
+ Convert a pydantic ValidationError into a DRF-style error response.
9
+ """
10
+ drf_errors: Dict[str, Any] = {}
11
+
12
+ for error in e.errors():
13
+ field_path = error['loc']
14
+ context_error = error.get('ctx', {}).get('error')
15
+
16
+ error_msg = context_error or ERROR_MESSAGES.get(error['type'], error['msg'])
17
+
18
+ # Convert the field path into a nested dictionary structure
19
+ current = drf_errors
20
+ for i, key in enumerate(field_path[:-1]):
21
+ current = current.setdefault(str(key), {})
22
+
23
+ # Set the error message at the final location
24
+ final_key = str(field_path[-1])
25
+ if final_key in current:
26
+ if isinstance(current[final_key], list):
27
+ current[final_key].append(error_msg)
28
+ else:
29
+ current[final_key] = [current[final_key], error_msg]
30
+ else:
31
+ current[final_key] = [error_msg]
32
+
33
+ return drf_errors
@@ -0,0 +1,7 @@
1
+ from pydantic_core import PydanticCustomError
2
+
3
+
4
+ def non_blank(value):
5
+ if not value.strip():
6
+ raise PydanticCustomError('blank', '필수 값을 확인해주세요.')
7
+ return value
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: synapse-sdk
3
- Version: 1.0.0a7
3
+ Version: 1.0.0a9
4
4
  Summary: synapse sdk
5
5
  Author-email: datamaker <developer@datamaker.io>
6
6
  License: MIT
@@ -1,25 +1,27 @@
1
1
  synapse_sdk/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- synapse_sdk/loggers.py,sha256=7wB18EZ092YQayNgXN_8JzS3vi0BjV5Xu0o2aTkcfNA,1468
2
+ synapse_sdk/loggers.py,sha256=avoceBBu9VuSNS_CC3M5X8rWmAlk5xCTYEAVqxAXzBM,4008
3
3
  synapse_sdk/cli/__init__.py,sha256=WmYGW1qZEXXIGJe3SGr8QjOStY4svuZKK1Lp_aPvtPs,140
4
4
  synapse_sdk/cli/create_plugin.py,sha256=egbW_92WwxfHz50Gy4znX5Bf5fxDdQj3GFyd0l3Y3SY,228
5
5
  synapse_sdk/clients/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
- synapse_sdk/clients/base.py,sha256=ybaSXqd0zWBBo_zZ01-2paWv5mAcQQpr7OUT0JbiBGE,2788
6
+ synapse_sdk/clients/base.py,sha256=vVt6Qi3R3lVnzCTD0WMRDYw99u2d1SMAqDXgkcvq4VM,3146
7
7
  synapse_sdk/clients/exceptions.py,sha256=ylv7x10eOp4aA3a48jwonnvqvkiYwzJYXjkVkRTAjwk,220
8
8
  synapse_sdk/clients/utils.py,sha256=8pPJTdzHiRPSbZMoQYHAgR2BAMO6u_R_jMV6a2p34iQ,392
9
- synapse_sdk/clients/agent/__init__.py,sha256=n4exZwc8gwuH6zqZUhstcD_sqIdm-ZoPvUcAxnCrnOQ,609
9
+ synapse_sdk/clients/agent/__init__.py,sha256=k5cGnN-dUAYYfeQDiRW4XEFWUgwSH9mwJFcvjzgwpOc,768
10
+ synapse_sdk/clients/agent/core.py,sha256=YicfkO0YvjDDOt1jNWoZ0mokrh8xxKibBL4qF5yOjKs,169
10
11
  synapse_sdk/clients/agent/service.py,sha256=NnRu0XPVRqry6PWN_8-3iuzdXx20zO1lPXw-yVWQbdg,1433
11
- synapse_sdk/clients/backend/__init__.py,sha256=850EjxNoGWXJpVZLRIaajrymBuW0MXiP3M3onRHDGbQ,800
12
- synapse_sdk/clients/backend/annotation.py,sha256=Zt1VA3fScYCxy_Ss1TEzqu7jYdNxlNBRYAjZfuEWOSI,989
13
- synapse_sdk/clients/backend/dataset.py,sha256=NfRU79_36YmLu6IrEpEfdsLBNdscpIfLdKJGUyNg00g,1744
14
- synapse_sdk/clients/backend/integration.py,sha256=jrymq6kyoM1CwWJuh8BcJAXoVFaY5cOaLTHYdFn4pwo,1410
15
- synapse_sdk/clients/backend/ml.py,sha256=l4rGLBZgLUYQOBePvWAoNyz-yZgJuhC-1KCFeZOYDuQ,1012
12
+ synapse_sdk/clients/backend/__init__.py,sha256=SVC7kDO_z_4YqpLDR-O_RrrxJFkWmMhJC8Hl_Mc5Sz4,830
13
+ synapse_sdk/clients/backend/annotation.py,sha256=eZc5EidgR_RfMGwvv1r1_mLkPdRd8e52c4zuuMjMX34,979
14
+ synapse_sdk/clients/backend/dataset.py,sha256=X8R71vff1gLytlEaOnKhBWjVQaZcIG_WYy32new4Vq0,1737
15
+ synapse_sdk/clients/backend/integration.py,sha256=Z0Vvhp4NsbP4W9esTH_0SDrPkzHy84t1joSinvtjWgI,1744
16
+ synapse_sdk/clients/backend/ml.py,sha256=SwA4_noRCZ1dVIzyXfrs2zHuRlQZdZt17o1We5QOxl8,1053
16
17
  synapse_sdk/plugins/__init__.py,sha256=9vsbYhxah4_ofTaG0x0qLFID_raHNkO57Y8A31Ws-lU,222
17
18
  synapse_sdk/plugins/enums.py,sha256=s59P6Oz2WAK9IX-kLVhNOvNKYJifKlWBhPpZbc9-ttE,486
18
- synapse_sdk/plugins/models.py,sha256=h57OcIDutEwN5ZK7id1AqSl8CuwpSWFC6gR9l0h4_7w,2598
19
+ synapse_sdk/plugins/exceptions.py,sha256=RFRz2SIHc4WmTd7sKZOMrQtD5nm4ldtb8CzG9VG65OE,167
20
+ synapse_sdk/plugins/models.py,sha256=pzzJl9zKL8v2XjBQC0UR-W9rG4Vyp95DzJ9MMG2W5L0,2835
19
21
  synapse_sdk/plugins/upload.py,sha256=SSCHUR4HJ_mdLN7mUkk5UlXmL9028SKP6tpBS2rpf0A,3175
20
22
  synapse_sdk/plugins/utils.py,sha256=n3s-zFnj4hrGWFtaBTJFbaupI8qUQL6S8_5YcbxOmeY,1482
21
23
  synapse_sdk/plugins/categories/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
22
- synapse_sdk/plugins/categories/base.py,sha256=nysyT-5UJSlFfqRrm_FAZ0XMSKEqiTC330vRYDUQ2P4,6043
24
+ synapse_sdk/plugins/categories/base.py,sha256=8WhcLa406V3rR31byhueQssRKeJsoO-kFWGzpre7O20,7062
23
25
  synapse_sdk/plugins/categories/decorators.py,sha256=Gw6T-UHwpCKrSt596X-g2sZbY_Z1zbbogowClj7Pr5Q,518
24
26
  synapse_sdk/plugins/categories/registry.py,sha256=KdQR8SUlLT-3kgYzDNWawS1uJnAhrcw2j4zFaTpilRs,636
25
27
  synapse_sdk/plugins/categories/templates.py,sha256=FF5FerhkZMeW1YcKLY5cylC0SkWSYdJODA_Qcm4OGYQ,887
@@ -40,12 +42,12 @@ synapse_sdk/plugins/categories/neural_net/actions/__init__.py,sha256=47DEQpj8HBS
40
42
  synapse_sdk/plugins/categories/neural_net/actions/deployment.py,sha256=U44sv_5UN2awfkbpyYPH9zOTEBhYn0FfcZwV7ywi-cA,827
41
43
  synapse_sdk/plugins/categories/neural_net/actions/inference.py,sha256=qavUxayUDgN5E5Ht0x3cqWF0uPNkwUiZlCmfo574xHE,334
42
44
  synapse_sdk/plugins/categories/neural_net/actions/test.py,sha256=JY25eg-Fo6WbgtMkGoo_qNqoaZkp3AQNEypJmeGzEog,320
43
- synapse_sdk/plugins/categories/neural_net/actions/train.py,sha256=Y-kXdDCVsLvXoZyjYrTFQYdCMdcTOtEprM39ix-yHmQ,1106
45
+ synapse_sdk/plugins/categories/neural_net/actions/train.py,sha256=pE0RTruXqX1Jdp4iWDpsRH80V7TnvMnZnAlLOYkRusA,4463
44
46
  synapse_sdk/plugins/categories/neural_net/templates/config.yaml,sha256=pvJFh_NHGZHHFfqrvZm6fHO5lsS7CudaIGboP6wbtY8,252
45
47
  synapse_sdk/plugins/categories/neural_net/templates/plugin/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
46
48
  synapse_sdk/plugins/categories/neural_net/templates/plugin/inference.py,sha256=glYYdRybG3vRe48bYIxvue7bCFGPlEZmYWRoyBsLdZw,123
47
49
  synapse_sdk/plugins/categories/neural_net/templates/plugin/test.py,sha256=kYyk7l4UtcDUAH4nkdVUGrHHHjxI4p1U13HSLnmGPyE,53
48
- synapse_sdk/plugins/categories/neural_net/templates/plugin/train.py,sha256=gtbUe6oVBNV7f_ZXQZ92rWnqRGw_rIUwNabQdgahF6w,504
50
+ synapse_sdk/plugins/categories/neural_net/templates/plugin/train.py,sha256=-QzWrfJGOk6EagQZmC1MMmiOEIr3X1sVGszMJjv5dFg,500
49
51
  synapse_sdk/plugins/categories/post_annotation/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
50
52
  synapse_sdk/plugins/categories/post_annotation/actions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
51
53
  synapse_sdk/plugins/categories/post_annotation/actions/post_annotation.py,sha256=dkPqOPPMUoxn7h1e-momdhqezhTJ7wLf-Dyx04zKfjw,347
@@ -58,29 +60,41 @@ synapse_sdk/plugins/categories/pre_annotation/actions/pre_annotation.py,sha256=6
58
60
  synapse_sdk/plugins/categories/pre_annotation/templates/config.yaml,sha256=bUaibKbb-kai3QAHWbCYbO8nT-DQpV-KhuBszB7i-hI,78
59
61
  synapse_sdk/plugins/categories/pre_annotation/templates/plugin/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
60
62
  synapse_sdk/plugins/categories/pre_annotation/templates/plugin/pre_annotation.py,sha256=HBHxHuv2gMBzDB2alFfrzI_SZ1Ztk6mo7eFbR5GqHKw,106
63
+ synapse_sdk/plugins/categories/smart_tool/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
64
+ synapse_sdk/plugins/categories/smart_tool/actions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
65
+ synapse_sdk/plugins/categories/smart_tool/actions/auto_label.py,sha256=MZKpLsCGzO1FJsE32D7O3gC2rrTpYZKP1x0oE-Uu8Mc,699
66
+ synapse_sdk/plugins/categories/smart_tool/templates/config.yaml,sha256=lTixdat8b3LZi_JeiUZfAN5oM5iFnqLD5M-xR_xF7M4,145
67
+ synapse_sdk/plugins/categories/smart_tool/templates/plugin/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
68
+ synapse_sdk/plugins/categories/smart_tool/templates/plugin/auto_label.py,sha256=eevNg0nOcYFR4z_L_R-sCvVOYoLWSAH1jwDkAf3YCjY,320
61
69
  synapse_sdk/plugins/cli/__init__.py,sha256=8ogaOhN-RbDNYHqziW8nLsNUxKkZwGkHBdKxTahcm3U,334
62
70
  synapse_sdk/plugins/cli/publish.py,sha256=ecX5vne2MuULon7JvH6NfiNcn7ccdv2SZUd9TakQzCI,1145
63
71
  synapse_sdk/plugins/cli/run.py,sha256=lw1KbsL-xTGllF4NtD2cq-Rh6HMbhi-sO862_Ds-sUo,2330
64
72
  synapse_sdk/plugins/templates/cookiecutter.json,sha256=NxOWk9A_v1pO0Ny4IYT9Cj5iiJ16--cIQrGC67QdR0I,396
65
73
  synapse_sdk/plugins/templates/hooks/post_gen_project.py,sha256=jqlYkY1O2TxIR-Vh3gnwILYy8k-D39Xx66d2KNQVMCs,147
66
74
  synapse_sdk/plugins/templates/hooks/pre_prompt.py,sha256=aOAMM623s0sKFGjTZaotAOYFvsNMxeii4tPyhOAFKVE,539
75
+ synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/.env,sha256=YlAG5YKwFEiwKm5NUbPsqvOJ9FJKoN0lbT3sxymBJpE,551
67
76
  synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/.env.dist,sha256=YlAG5YKwFEiwKm5NUbPsqvOJ9FJKoN0lbT3sxymBJpE,551
68
77
  synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/.gitignore,sha256=RvBmUbwmzgv0vYzYk2aQ0HpvKyPj6sCpBqsrthCuA4s,243
69
78
  synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/.pre-commit-config.yaml,sha256=p0yZwCUC8tYS1B0GPkjKiXYoRY9EZlq_ejFP98hB50g,154
70
79
  synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/README.md,sha256=ETBZv_2Ocgzn4Fe3o5Y842mZiz00ABuAalrXpNVnWU0,56
71
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/config.yaml,sha256=3Sv59w4vAgFasJEn5z7WNborTVlotrqOOVnCLEEz5JI,459
80
+ synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/config.yaml,sha256=CRfKjPJ6K2UlYbcHMSAYUGzn2TGmV7kDbZmKHwNuq3w,210
81
+ synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/main.py,sha256=fPFaIPBwskS84YvnmDGERXn3wC5z_yV_f4NbisVvBfQ,79
72
82
  synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/pyproject.toml,sha256=Usgd80tHZAD1Ug5MAjPfETUZxtKKgZW-xovFEAEbQDo,317
73
83
  synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/requirements.txt,sha256=F5UwinpTLQFfyakFGTFxgBOo4H-EKD9d4e77WKOPHhk,17
74
84
  synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/plugin/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
75
85
  synapse_sdk/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
76
86
  synapse_sdk/utils/debug.py,sha256=46sMFQLg_JSRUCymnT3wgszG1QsgrocRGiBjVX38r50,53
77
- synapse_sdk/utils/file.py,sha256=Iptk_DCPsmJzqAABCD3vC6z1yG74fKb5x81LnUCZzYo,1916
87
+ synapse_sdk/utils/file.py,sha256=nuU6POe0F6_WvIB26X9Q6dpcwgys8unt5Qv66n0Bmg4,2482
78
88
  synapse_sdk/utils/module_loading.py,sha256=chHpU-BZjtYaTBD_q0T7LcKWtqKvYBS4L0lPlKkoMQ8,1020
79
89
  synapse_sdk/utils/storage.py,sha256=a8OVbd38ATr0El4G4kuV07lr_tJZrpIJBSy4GHb0qZ8,2581
80
90
  synapse_sdk/utils/string.py,sha256=rEwuZ9SAaZLcQ8TYiwNKr1h2u4CfnrQx7SUL8NWmChg,216
81
- synapse_sdk-1.0.0a7.dist-info/LICENSE,sha256=bKzmC5YAg4V1Fhl8OO_tqY8j62hgdncAkN7VrdjmrGk,1101
82
- synapse_sdk-1.0.0a7.dist-info/METADATA,sha256=T3_Tc4DYIhZvOAN1-K2a9R1Vdd4rrs_k6nML4TdAgtw,567
83
- synapse_sdk-1.0.0a7.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
84
- synapse_sdk-1.0.0a7.dist-info/entry_points.txt,sha256=VNptJoGoNJI8yLXfBmhgUefMsmGI0m3-0YoMvrOgbxo,48
85
- synapse_sdk-1.0.0a7.dist-info/top_level.txt,sha256=ytgJMRK1slVOKUpgcw3LEyHHP7S34J6n_gJzdkcSsw8,12
86
- synapse_sdk-1.0.0a7.dist-info/RECORD,,
91
+ synapse_sdk/utils/pydantic/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
92
+ synapse_sdk/utils/pydantic/config.py,sha256=1vYOcUI35GslfD1rrqhFkNXXJOXt4IDqOPSx9VWGfNE,123
93
+ synapse_sdk/utils/pydantic/errors.py,sha256=0v0T12eQBr1KrFiEOBu6KMaPK4aPEGEC6etPJGoR5b4,1061
94
+ synapse_sdk/utils/pydantic/validators.py,sha256=G47P8ObPhsePmd_QZDK8EdPnik2CbaYzr_N4Z6En8dc,193
95
+ synapse_sdk-1.0.0a9.dist-info/LICENSE,sha256=bKzmC5YAg4V1Fhl8OO_tqY8j62hgdncAkN7VrdjmrGk,1101
96
+ synapse_sdk-1.0.0a9.dist-info/METADATA,sha256=4pnZMrqZytZAvodVHryUt8WTbLnPwreD2iYPr7XTvvE,567
97
+ synapse_sdk-1.0.0a9.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
98
+ synapse_sdk-1.0.0a9.dist-info/entry_points.txt,sha256=VNptJoGoNJI8yLXfBmhgUefMsmGI0m3-0YoMvrOgbxo,48
99
+ synapse_sdk-1.0.0a9.dist-info/top_level.txt,sha256=ytgJMRK1slVOKUpgcw3LEyHHP7S34J6n_gJzdkcSsw8,12
100
+ synapse_sdk-1.0.0a9.dist-info/RECORD,,