dtlpy 1.115.44__py3-none-any.whl → 1.116.6__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.
- dtlpy/__init__.py +491 -491
- dtlpy/__version__.py +1 -1
- dtlpy/assets/__init__.py +26 -26
- dtlpy/assets/code_server/config.yaml +2 -2
- dtlpy/assets/code_server/installation.sh +24 -24
- dtlpy/assets/code_server/launch.json +13 -13
- dtlpy/assets/code_server/settings.json +2 -2
- dtlpy/assets/main.py +53 -53
- dtlpy/assets/main_partial.py +18 -18
- dtlpy/assets/mock.json +11 -11
- dtlpy/assets/model_adapter.py +83 -83
- dtlpy/assets/package.json +61 -61
- dtlpy/assets/package_catalog.json +29 -29
- dtlpy/assets/package_gitignore +307 -307
- dtlpy/assets/service_runners/__init__.py +33 -33
- dtlpy/assets/service_runners/converter.py +96 -96
- dtlpy/assets/service_runners/multi_method.py +49 -49
- dtlpy/assets/service_runners/multi_method_annotation.py +54 -54
- dtlpy/assets/service_runners/multi_method_dataset.py +55 -55
- dtlpy/assets/service_runners/multi_method_item.py +52 -52
- dtlpy/assets/service_runners/multi_method_json.py +52 -52
- dtlpy/assets/service_runners/single_method.py +37 -37
- dtlpy/assets/service_runners/single_method_annotation.py +43 -43
- dtlpy/assets/service_runners/single_method_dataset.py +43 -43
- dtlpy/assets/service_runners/single_method_item.py +41 -41
- dtlpy/assets/service_runners/single_method_json.py +42 -42
- dtlpy/assets/service_runners/single_method_multi_input.py +45 -45
- dtlpy/assets/voc_annotation_template.xml +23 -23
- dtlpy/caches/base_cache.py +32 -32
- dtlpy/caches/cache.py +473 -473
- dtlpy/caches/dl_cache.py +201 -201
- dtlpy/caches/filesystem_cache.py +89 -89
- dtlpy/caches/redis_cache.py +84 -84
- dtlpy/dlp/__init__.py +20 -20
- dtlpy/dlp/cli_utilities.py +367 -367
- dtlpy/dlp/command_executor.py +764 -764
- dtlpy/dlp/dlp +1 -1
- dtlpy/dlp/dlp.bat +1 -1
- dtlpy/dlp/dlp.py +128 -128
- dtlpy/dlp/parser.py +651 -651
- dtlpy/entities/__init__.py +83 -83
- dtlpy/entities/analytic.py +347 -347
- dtlpy/entities/annotation.py +1879 -1879
- dtlpy/entities/annotation_collection.py +699 -699
- dtlpy/entities/annotation_definitions/__init__.py +20 -20
- dtlpy/entities/annotation_definitions/base_annotation_definition.py +100 -100
- dtlpy/entities/annotation_definitions/box.py +195 -195
- dtlpy/entities/annotation_definitions/classification.py +67 -67
- dtlpy/entities/annotation_definitions/comparison.py +72 -72
- dtlpy/entities/annotation_definitions/cube.py +204 -204
- dtlpy/entities/annotation_definitions/cube_3d.py +149 -149
- dtlpy/entities/annotation_definitions/description.py +32 -32
- dtlpy/entities/annotation_definitions/ellipse.py +124 -124
- dtlpy/entities/annotation_definitions/free_text.py +62 -62
- dtlpy/entities/annotation_definitions/gis.py +69 -69
- dtlpy/entities/annotation_definitions/note.py +139 -139
- dtlpy/entities/annotation_definitions/point.py +117 -117
- dtlpy/entities/annotation_definitions/polygon.py +182 -182
- dtlpy/entities/annotation_definitions/polyline.py +111 -111
- dtlpy/entities/annotation_definitions/pose.py +92 -92
- dtlpy/entities/annotation_definitions/ref_image.py +86 -86
- dtlpy/entities/annotation_definitions/segmentation.py +240 -240
- dtlpy/entities/annotation_definitions/subtitle.py +34 -34
- dtlpy/entities/annotation_definitions/text.py +85 -85
- dtlpy/entities/annotation_definitions/undefined_annotation.py +74 -74
- dtlpy/entities/app.py +220 -220
- dtlpy/entities/app_module.py +107 -107
- dtlpy/entities/artifact.py +174 -174
- dtlpy/entities/assignment.py +399 -399
- dtlpy/entities/base_entity.py +214 -214
- dtlpy/entities/bot.py +113 -113
- dtlpy/entities/codebase.py +292 -292
- dtlpy/entities/collection.py +38 -38
- dtlpy/entities/command.py +169 -169
- dtlpy/entities/compute.py +449 -449
- dtlpy/entities/dataset.py +1299 -1299
- dtlpy/entities/directory_tree.py +44 -44
- dtlpy/entities/dpk.py +470 -470
- dtlpy/entities/driver.py +235 -235
- dtlpy/entities/execution.py +397 -397
- dtlpy/entities/feature.py +124 -124
- dtlpy/entities/feature_set.py +145 -145
- dtlpy/entities/filters.py +798 -798
- dtlpy/entities/gis_item.py +107 -107
- dtlpy/entities/integration.py +184 -184
- dtlpy/entities/item.py +959 -959
- dtlpy/entities/label.py +123 -123
- dtlpy/entities/links.py +85 -85
- dtlpy/entities/message.py +175 -175
- dtlpy/entities/model.py +684 -684
- dtlpy/entities/node.py +1005 -1005
- dtlpy/entities/ontology.py +810 -803
- dtlpy/entities/organization.py +287 -287
- dtlpy/entities/package.py +657 -657
- dtlpy/entities/package_defaults.py +5 -5
- dtlpy/entities/package_function.py +185 -185
- dtlpy/entities/package_module.py +113 -113
- dtlpy/entities/package_slot.py +118 -118
- dtlpy/entities/paged_entities.py +299 -299
- dtlpy/entities/pipeline.py +624 -624
- dtlpy/entities/pipeline_execution.py +279 -279
- dtlpy/entities/project.py +394 -394
- dtlpy/entities/prompt_item.py +505 -505
- dtlpy/entities/recipe.py +301 -301
- dtlpy/entities/reflect_dict.py +102 -102
- dtlpy/entities/resource_execution.py +138 -138
- dtlpy/entities/service.py +963 -963
- dtlpy/entities/service_driver.py +117 -117
- dtlpy/entities/setting.py +294 -294
- dtlpy/entities/task.py +495 -495
- dtlpy/entities/time_series.py +143 -143
- dtlpy/entities/trigger.py +426 -426
- dtlpy/entities/user.py +118 -118
- dtlpy/entities/webhook.py +124 -124
- dtlpy/examples/__init__.py +19 -19
- dtlpy/examples/add_labels.py +135 -135
- dtlpy/examples/add_metadata_to_item.py +21 -21
- dtlpy/examples/annotate_items_using_model.py +65 -65
- dtlpy/examples/annotate_video_using_model_and_tracker.py +75 -75
- dtlpy/examples/annotations_convert_to_voc.py +9 -9
- dtlpy/examples/annotations_convert_to_yolo.py +9 -9
- dtlpy/examples/convert_annotation_types.py +51 -51
- dtlpy/examples/converter.py +143 -143
- dtlpy/examples/copy_annotations.py +22 -22
- dtlpy/examples/copy_folder.py +31 -31
- dtlpy/examples/create_annotations.py +51 -51
- dtlpy/examples/create_video_annotations.py +83 -83
- dtlpy/examples/delete_annotations.py +26 -26
- dtlpy/examples/filters.py +113 -113
- dtlpy/examples/move_item.py +23 -23
- dtlpy/examples/play_video_annotation.py +13 -13
- dtlpy/examples/show_item_and_mask.py +53 -53
- dtlpy/examples/triggers.py +49 -49
- dtlpy/examples/upload_batch_of_items.py +20 -20
- dtlpy/examples/upload_items_and_custom_format_annotations.py +55 -55
- dtlpy/examples/upload_items_with_modalities.py +43 -43
- dtlpy/examples/upload_segmentation_annotations_from_mask_image.py +44 -44
- dtlpy/examples/upload_yolo_format_annotations.py +70 -70
- dtlpy/exceptions.py +125 -125
- dtlpy/miscellaneous/__init__.py +20 -20
- dtlpy/miscellaneous/dict_differ.py +95 -95
- dtlpy/miscellaneous/git_utils.py +217 -217
- dtlpy/miscellaneous/json_utils.py +14 -14
- dtlpy/miscellaneous/list_print.py +105 -105
- dtlpy/miscellaneous/zipping.py +130 -130
- dtlpy/ml/__init__.py +20 -20
- dtlpy/ml/base_feature_extractor_adapter.py +27 -27
- dtlpy/ml/base_model_adapter.py +1257 -1230
- dtlpy/ml/metrics.py +461 -461
- dtlpy/ml/predictions_utils.py +274 -274
- dtlpy/ml/summary_writer.py +57 -57
- dtlpy/ml/train_utils.py +60 -60
- dtlpy/new_instance.py +252 -252
- dtlpy/repositories/__init__.py +56 -56
- dtlpy/repositories/analytics.py +85 -85
- dtlpy/repositories/annotations.py +916 -916
- dtlpy/repositories/apps.py +383 -383
- dtlpy/repositories/artifacts.py +452 -452
- dtlpy/repositories/assignments.py +599 -599
- dtlpy/repositories/bots.py +213 -213
- dtlpy/repositories/codebases.py +559 -559
- dtlpy/repositories/collections.py +332 -332
- dtlpy/repositories/commands.py +152 -152
- dtlpy/repositories/compositions.py +61 -61
- dtlpy/repositories/computes.py +439 -439
- dtlpy/repositories/datasets.py +1504 -1504
- dtlpy/repositories/downloader.py +976 -923
- dtlpy/repositories/dpks.py +433 -433
- dtlpy/repositories/drivers.py +482 -482
- dtlpy/repositories/executions.py +815 -815
- dtlpy/repositories/feature_sets.py +226 -226
- dtlpy/repositories/features.py +255 -255
- dtlpy/repositories/integrations.py +484 -484
- dtlpy/repositories/items.py +912 -912
- dtlpy/repositories/messages.py +94 -94
- dtlpy/repositories/models.py +1000 -1000
- dtlpy/repositories/nodes.py +80 -80
- dtlpy/repositories/ontologies.py +511 -511
- dtlpy/repositories/organizations.py +525 -525
- dtlpy/repositories/packages.py +1941 -1941
- dtlpy/repositories/pipeline_executions.py +451 -451
- dtlpy/repositories/pipelines.py +640 -640
- dtlpy/repositories/projects.py +539 -539
- dtlpy/repositories/recipes.py +419 -399
- dtlpy/repositories/resource_executions.py +137 -137
- dtlpy/repositories/schema.py +120 -120
- dtlpy/repositories/service_drivers.py +213 -213
- dtlpy/repositories/services.py +1704 -1704
- dtlpy/repositories/settings.py +339 -339
- dtlpy/repositories/tasks.py +1477 -1477
- dtlpy/repositories/times_series.py +278 -278
- dtlpy/repositories/triggers.py +536 -536
- dtlpy/repositories/upload_element.py +257 -257
- dtlpy/repositories/uploader.py +661 -661
- dtlpy/repositories/webhooks.py +249 -249
- dtlpy/services/__init__.py +22 -22
- dtlpy/services/aihttp_retry.py +131 -131
- dtlpy/services/api_client.py +1785 -1785
- dtlpy/services/api_reference.py +40 -40
- dtlpy/services/async_utils.py +133 -133
- dtlpy/services/calls_counter.py +44 -44
- dtlpy/services/check_sdk.py +68 -68
- dtlpy/services/cookie.py +115 -115
- dtlpy/services/create_logger.py +156 -156
- dtlpy/services/events.py +84 -84
- dtlpy/services/logins.py +235 -235
- dtlpy/services/reporter.py +256 -256
- dtlpy/services/service_defaults.py +91 -91
- dtlpy/utilities/__init__.py +20 -20
- dtlpy/utilities/annotations/__init__.py +16 -16
- dtlpy/utilities/annotations/annotation_converters.py +269 -269
- dtlpy/utilities/base_package_runner.py +285 -264
- dtlpy/utilities/converter.py +1650 -1650
- dtlpy/utilities/dataset_generators/__init__.py +1 -1
- dtlpy/utilities/dataset_generators/dataset_generator.py +670 -670
- dtlpy/utilities/dataset_generators/dataset_generator_tensorflow.py +23 -23
- dtlpy/utilities/dataset_generators/dataset_generator_torch.py +21 -21
- dtlpy/utilities/local_development/__init__.py +1 -1
- dtlpy/utilities/local_development/local_session.py +179 -179
- dtlpy/utilities/reports/__init__.py +2 -2
- dtlpy/utilities/reports/figures.py +343 -343
- dtlpy/utilities/reports/report.py +71 -71
- dtlpy/utilities/videos/__init__.py +17 -17
- dtlpy/utilities/videos/video_player.py +598 -598
- dtlpy/utilities/videos/videos.py +470 -470
- {dtlpy-1.115.44.data → dtlpy-1.116.6.data}/scripts/dlp +1 -1
- dtlpy-1.116.6.data/scripts/dlp.bat +2 -0
- {dtlpy-1.115.44.data → dtlpy-1.116.6.data}/scripts/dlp.py +128 -128
- {dtlpy-1.115.44.dist-info → dtlpy-1.116.6.dist-info}/METADATA +186 -186
- dtlpy-1.116.6.dist-info/RECORD +239 -0
- {dtlpy-1.115.44.dist-info → dtlpy-1.116.6.dist-info}/WHEEL +1 -1
- {dtlpy-1.115.44.dist-info → dtlpy-1.116.6.dist-info}/licenses/LICENSE +200 -200
- tests/features/environment.py +551 -551
- dtlpy/assets/__pycache__/__init__.cpython-310.pyc +0 -0
- dtlpy-1.115.44.data/scripts/dlp.bat +0 -2
- dtlpy-1.115.44.dist-info/RECORD +0 -240
- {dtlpy-1.115.44.dist-info → dtlpy-1.116.6.dist-info}/entry_points.txt +0 -0
- {dtlpy-1.115.44.dist-info → dtlpy-1.116.6.dist-info}/top_level.txt +0 -0
dtlpy/repositories/codebases.py
CHANGED
|
@@ -1,559 +1,559 @@
|
|
|
1
|
-
import hashlib
|
|
2
|
-
import logging
|
|
3
|
-
import os
|
|
4
|
-
import io
|
|
5
|
-
import random
|
|
6
|
-
from typing import List
|
|
7
|
-
|
|
8
|
-
from .. import entities, PlatformException, exceptions, repositories, miscellaneous
|
|
9
|
-
from ..services.api_client import ApiClient
|
|
10
|
-
from ..services.api_client import client as client_api
|
|
11
|
-
|
|
12
|
-
logger = logging.getLogger(name='dtlpy')
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
class Codebases:
|
|
16
|
-
"""
|
|
17
|
-
Codebase Repository
|
|
18
|
-
|
|
19
|
-
The Codebases class allows the user to manage codebases and their properties.
|
|
20
|
-
The codebase is the code the user uploads for the user's packages to run.
|
|
21
|
-
Read more about `codebase <https://developers.dataloop.ai/tutorials/faas/introduction/chapter/>`_ in our FaaS (function as a service).
|
|
22
|
-
"""
|
|
23
|
-
|
|
24
|
-
def __init__(self,
|
|
25
|
-
client_api: ApiClient,
|
|
26
|
-
project: entities.Project = None,
|
|
27
|
-
dataset: entities.Dataset = None,
|
|
28
|
-
project_id: str = None):
|
|
29
|
-
self._client_api = client_api
|
|
30
|
-
self._project = project
|
|
31
|
-
self._project_id = project_id
|
|
32
|
-
self._dataset = dataset
|
|
33
|
-
self._items_repository = None
|
|
34
|
-
self.git_utils = miscellaneous.GitUtils()
|
|
35
|
-
|
|
36
|
-
@property
|
|
37
|
-
def items_repository(self) -> repositories.Items:
|
|
38
|
-
if self._items_repository is None:
|
|
39
|
-
if self._dataset is not None or self._project is not None:
|
|
40
|
-
self._items_repository = self.dataset.items
|
|
41
|
-
else:
|
|
42
|
-
self._items_repository = repositories.Items(client_api=client_api)
|
|
43
|
-
assert isinstance(self._items_repository, repositories.Items)
|
|
44
|
-
return self._items_repository
|
|
45
|
-
|
|
46
|
-
@property
|
|
47
|
-
def project(self) -> entities.Project:
|
|
48
|
-
if self._project is None:
|
|
49
|
-
# project is None - try dataset
|
|
50
|
-
if self._dataset is None:
|
|
51
|
-
# dataset is None - try from project id
|
|
52
|
-
if self._project_id is None:
|
|
53
|
-
raise PlatformException(error='400',
|
|
54
|
-
message='Cannot perform this action without a project')
|
|
55
|
-
else:
|
|
56
|
-
self._project = repositories.Projects(client_api=self._client_api).get(project_id=self._project_id)
|
|
57
|
-
else:
|
|
58
|
-
# dataset is not None - take project from there
|
|
59
|
-
self._project = self.dataset.project
|
|
60
|
-
assert isinstance(self._project, entities.Project)
|
|
61
|
-
return self._project
|
|
62
|
-
|
|
63
|
-
@property
|
|
64
|
-
def dataset(self) -> entities.Dataset:
|
|
65
|
-
if self._dataset is None:
|
|
66
|
-
# get dataset from project
|
|
67
|
-
try:
|
|
68
|
-
self._dataset = self.project.datasets._get_binaries_dataset()
|
|
69
|
-
except exceptions.NotFound:
|
|
70
|
-
raise ValueError('Missing "Binaries" dataset in the project. Please contact support for help')
|
|
71
|
-
assert isinstance(self._dataset, entities.Dataset)
|
|
72
|
-
return self._dataset
|
|
73
|
-
|
|
74
|
-
@dataset.setter
|
|
75
|
-
def dataset(self, dataset: entities.Dataset):
|
|
76
|
-
if not isinstance(dataset, entities.Dataset):
|
|
77
|
-
raise ValueError('Must input a valid Dataset entity')
|
|
78
|
-
self._dataset = dataset
|
|
79
|
-
|
|
80
|
-
@staticmethod
|
|
81
|
-
def __file_hash(filepath):
|
|
82
|
-
m = hashlib.md5()
|
|
83
|
-
with open(filepath, 'rb') as f:
|
|
84
|
-
for chunk in iter(lambda: f.read(4096), b''):
|
|
85
|
-
m.update(chunk)
|
|
86
|
-
return m.hexdigest()
|
|
87
|
-
|
|
88
|
-
def list_versions(self, codebase_name: str) -> entities.PagedEntities:
|
|
89
|
-
"""
|
|
90
|
-
List all codebase versions.
|
|
91
|
-
|
|
92
|
-
**Prerequisites**: You must be in the role of an *owner* or *developer*. You must have a package.
|
|
93
|
-
|
|
94
|
-
**Example**:
|
|
95
|
-
|
|
96
|
-
.. code-block:: python
|
|
97
|
-
|
|
98
|
-
package.codebases.list_versions(codebase_name='codebase_name')
|
|
99
|
-
|
|
100
|
-
:param str codebase_name: code base name
|
|
101
|
-
:return: list of versions
|
|
102
|
-
:rtype: list
|
|
103
|
-
"""
|
|
104
|
-
filters = entities.Filters()
|
|
105
|
-
filters.add(field='filename', values='/codebases/{}/*'.format(codebase_name))
|
|
106
|
-
versions = self.items_repository.list(filters=filters)
|
|
107
|
-
return versions
|
|
108
|
-
|
|
109
|
-
def list(self) -> entities.PagedEntities:
|
|
110
|
-
"""
|
|
111
|
-
List all codebases.
|
|
112
|
-
|
|
113
|
-
**Prerequisites**: You must be in the role of an *owner* or *developer*. You must have a package.
|
|
114
|
-
|
|
115
|
-
**Example**:
|
|
116
|
-
|
|
117
|
-
.. code-block:: python
|
|
118
|
-
|
|
119
|
-
package.codebases.list()
|
|
120
|
-
|
|
121
|
-
:return: Paged entity
|
|
122
|
-
:rtype: dtlpy.entities.paged_entities.PagedEntities
|
|
123
|
-
"""
|
|
124
|
-
filters = entities.Filters()
|
|
125
|
-
filters.add(field='filename', values='/codebases/*')
|
|
126
|
-
filters.add(field='type', values='dir')
|
|
127
|
-
codebases = self.items_repository.list(filters=filters)
|
|
128
|
-
return codebases
|
|
129
|
-
|
|
130
|
-
def get(self,
|
|
131
|
-
codebase_name: str = None,
|
|
132
|
-
codebase_id: str = None,
|
|
133
|
-
version: str = None):
|
|
134
|
-
"""
|
|
135
|
-
Get a Codebase object to use in your code.
|
|
136
|
-
|
|
137
|
-
**Prerequisites**: You must be in the role of an *owner* or *developer*. You must have a package.
|
|
138
|
-
|
|
139
|
-
**Example**:
|
|
140
|
-
|
|
141
|
-
.. code-block:: python
|
|
142
|
-
|
|
143
|
-
package.codebases.get(codebase_name='codebase_name')
|
|
144
|
-
|
|
145
|
-
:param str codebase_name: optional - search by name
|
|
146
|
-
:param str codebase_id: optional - search by id
|
|
147
|
-
:param str version: codebase version. default is latest. options: "all", "latest" or ver number - "10"
|
|
148
|
-
:return: Codebase object
|
|
149
|
-
"""
|
|
150
|
-
if codebase_id is not None:
|
|
151
|
-
matched_version = self.items_repository.get(item_id=codebase_id)
|
|
152
|
-
# verify input codebase name is same as the given id
|
|
153
|
-
if codebase_name is not None and matched_version.name != codebase_name:
|
|
154
|
-
logger.warning(
|
|
155
|
-
"Mismatch found in codebases.get: codebase_name is different then codebase.name: "
|
|
156
|
-
"{!r} != {!r}".format(
|
|
157
|
-
codebase_name,
|
|
158
|
-
matched_version.name))
|
|
159
|
-
codebase = entities.Codebase(type='item',
|
|
160
|
-
client_api=self._client_api,
|
|
161
|
-
itemId=matched_version.id,
|
|
162
|
-
item=matched_version)
|
|
163
|
-
return codebase
|
|
164
|
-
|
|
165
|
-
if codebase_name is None:
|
|
166
|
-
raise PlatformException(error='400', message='Either "codebase_name" or "codebase_id" is needed')
|
|
167
|
-
if version is None:
|
|
168
|
-
version = 'latest'
|
|
169
|
-
|
|
170
|
-
if version not in ['all', 'latest']:
|
|
171
|
-
try:
|
|
172
|
-
matched_version = self.items_repository.get(
|
|
173
|
-
filepath='/codebases/{}/{}.zip'.format(codebase_name, version))
|
|
174
|
-
except Exception:
|
|
175
|
-
raise PlatformException(error='404',
|
|
176
|
-
message='No matching version was found. version: {}'.format(version))
|
|
177
|
-
codebase = entities.Codebase(type='item',
|
|
178
|
-
client_api=self._client_api,
|
|
179
|
-
itemId=matched_version.id,
|
|
180
|
-
item=matched_version)
|
|
181
|
-
return codebase
|
|
182
|
-
|
|
183
|
-
# get all or latest
|
|
184
|
-
versions_pages = self.list_versions(codebase_name=codebase_name)
|
|
185
|
-
if versions_pages.items_count == 0:
|
|
186
|
-
raise PlatformException(error='404', message='No codebase was found. name: {}'.format(codebase_name))
|
|
187
|
-
else:
|
|
188
|
-
if version == 'all':
|
|
189
|
-
codebase = [entities.Codebase(type='item',
|
|
190
|
-
client_api=self._client_api,
|
|
191
|
-
itemId=mv.id,
|
|
192
|
-
item=mv) for mv in versions_pages.all()]
|
|
193
|
-
elif version == 'latest':
|
|
194
|
-
max_ver = -1
|
|
195
|
-
matched_version = None
|
|
196
|
-
for page in versions_pages:
|
|
197
|
-
for ver in page:
|
|
198
|
-
if ver.type == 'dir':
|
|
199
|
-
continue
|
|
200
|
-
# extract version from filepath
|
|
201
|
-
ver_int = int(os.path.splitext(ver.name)[0])
|
|
202
|
-
if ver_int > max_ver:
|
|
203
|
-
max_ver = ver_int
|
|
204
|
-
matched_version = ver
|
|
205
|
-
if matched_version is None:
|
|
206
|
-
raise PlatformException(error='404',
|
|
207
|
-
message='No codebase was found. name: {}'.format(codebase_name))
|
|
208
|
-
else:
|
|
209
|
-
codebase = entities.Codebase(type='item',
|
|
210
|
-
client_api=self._client_api,
|
|
211
|
-
itemId=matched_version.id,
|
|
212
|
-
item=matched_version)
|
|
213
|
-
else:
|
|
214
|
-
raise PlatformException(error='404', message='Unknown version string: {}'.format(version))
|
|
215
|
-
|
|
216
|
-
return codebase
|
|
217
|
-
|
|
218
|
-
@staticmethod
|
|
219
|
-
def get_current_version(all_versions_pages, zip_md):
|
|
220
|
-
"""
|
|
221
|
-
This method returns the current version of the codebase and other versions found.
|
|
222
|
-
|
|
223
|
-
**Prerequisites**: You must be in the role of an *owner* or *developer*. You must have a package.
|
|
224
|
-
|
|
225
|
-
:param codebase all_versions_pages: codebase object
|
|
226
|
-
:param zip_md: zipped file of codebase
|
|
227
|
-
:return: current version and all versions found of codebase
|
|
228
|
-
:rtype: int, int
|
|
229
|
-
|
|
230
|
-
**Example**:
|
|
231
|
-
|
|
232
|
-
.. code-block:: python
|
|
233
|
-
|
|
234
|
-
package.codebases.get_current_version(all_versions_pages='codebase_entity', zip_md='path')
|
|
235
|
-
"""
|
|
236
|
-
latest_version = 0
|
|
237
|
-
same_version_found = None
|
|
238
|
-
# go over all existing versions
|
|
239
|
-
for v_item in all_versions_pages:
|
|
240
|
-
# get latest version
|
|
241
|
-
if int(os.path.splitext(v_item.item.name)[0]) > latest_version:
|
|
242
|
-
latest_version = int(os.path.splitext(v_item.item.name)[0])
|
|
243
|
-
# check md5 to find same codebase
|
|
244
|
-
if 'md5' in v_item.item.metadata['system'] and v_item.item.metadata['system']['md5'] == zip_md:
|
|
245
|
-
same_version_found = v_item
|
|
246
|
-
break
|
|
247
|
-
return latest_version + 1, same_version_found
|
|
248
|
-
|
|
249
|
-
def pack(self,
|
|
250
|
-
directory: str,
|
|
251
|
-
name: str = None,
|
|
252
|
-
extension: str = 'zip',
|
|
253
|
-
description: str = '',
|
|
254
|
-
ignore_directories: List[str] = None,
|
|
255
|
-
ignore_max_file_size: bool = False):
|
|
256
|
-
"""
|
|
257
|
-
Zip a local code directory and post to codebases.
|
|
258
|
-
|
|
259
|
-
**Prerequisites**: You must be in the role of an *owner* or *developer*. You must have a package.
|
|
260
|
-
|
|
261
|
-
:param str directory: local directory to pack
|
|
262
|
-
:param str name: codebase name
|
|
263
|
-
:param str extension: the extension of the file
|
|
264
|
-
:param str description: codebase description
|
|
265
|
-
:param list[str] ignore_directories: directories to ignore.
|
|
266
|
-
:return: Codebase object
|
|
267
|
-
:rtype: dtlpy.entities.codebase.Codebase
|
|
268
|
-
|
|
269
|
-
**Example**:
|
|
270
|
-
|
|
271
|
-
.. code-block:: python
|
|
272
|
-
|
|
273
|
-
package.codebases.pack(directory='path_dir', name='codebase_name')
|
|
274
|
-
"""
|
|
275
|
-
# create/get .dataloop dir
|
|
276
|
-
cwd = os.getcwd()
|
|
277
|
-
dl_dir = os.path.join(cwd, '.dataloop')
|
|
278
|
-
if not os.path.isdir(dl_dir):
|
|
279
|
-
os.mkdir(dl_dir)
|
|
280
|
-
|
|
281
|
-
# get codebase name
|
|
282
|
-
if name is None:
|
|
283
|
-
name = os.path.basename(directory)
|
|
284
|
-
|
|
285
|
-
# create/get dist folder
|
|
286
|
-
zip_filename = os.path.join(dl_dir, '{}_{}.{}'.format(name, str(random.randrange(0, 1000)), extension))
|
|
287
|
-
|
|
288
|
-
try:
|
|
289
|
-
if not os.path.isdir(directory):
|
|
290
|
-
raise PlatformException(error='400', message='Not a directory: {}'.format(directory))
|
|
291
|
-
directory = os.path.abspath(directory)
|
|
292
|
-
|
|
293
|
-
# create zipfile
|
|
294
|
-
miscellaneous.Zipping.zip_directory(zip_filename=zip_filename,
|
|
295
|
-
directory=directory,
|
|
296
|
-
ignore_directories=ignore_directories,
|
|
297
|
-
ignore_max_file_size=ignore_max_file_size)
|
|
298
|
-
zip_md = self.__file_hash(zip_filename)
|
|
299
|
-
|
|
300
|
-
# get latest version
|
|
301
|
-
same_version_found = None
|
|
302
|
-
try:
|
|
303
|
-
all_versions_pages = self.get(codebase_name=name, version='all')
|
|
304
|
-
except exceptions.NotFound:
|
|
305
|
-
all_versions_pages = None
|
|
306
|
-
if all_versions_pages is None:
|
|
307
|
-
# no codebase with that name - create new version
|
|
308
|
-
current_version = 0
|
|
309
|
-
else:
|
|
310
|
-
current_version, same_version_found = self.get_current_version(all_versions_pages=all_versions_pages,
|
|
311
|
-
zip_md=zip_md)
|
|
312
|
-
|
|
313
|
-
if same_version_found is not None:
|
|
314
|
-
# same md5 hash file found in version - return the matched version
|
|
315
|
-
codebase = same_version_found
|
|
316
|
-
else:
|
|
317
|
-
# no matched version was found - create a new version
|
|
318
|
-
# read from zipped file
|
|
319
|
-
with open(zip_filename, 'rb') as f:
|
|
320
|
-
buffer = io.BytesIO(f.read())
|
|
321
|
-
buffer.name = '{}.{}'.format(str(current_version), extension)
|
|
322
|
-
|
|
323
|
-
# upload item
|
|
324
|
-
item = self.items_repository.upload(local_path=buffer,
|
|
325
|
-
remote_path='/codebases/{}'.format(name))
|
|
326
|
-
if isinstance(item, list) and len(item) == 0:
|
|
327
|
-
raise PlatformException(error='400', message='Failed upload codebase, check log file for details')
|
|
328
|
-
|
|
329
|
-
# add source code to metadata
|
|
330
|
-
if 'system' not in item.metadata:
|
|
331
|
-
item.metadata['system'] = dict()
|
|
332
|
-
item.metadata['system']['description'] = description
|
|
333
|
-
item.metadata['system']['md5'] = zip_md
|
|
334
|
-
|
|
335
|
-
# add git info to metadata
|
|
336
|
-
if miscellaneous.GitUtils.is_git_repo(path=directory):
|
|
337
|
-
# create 'git' field in metadata
|
|
338
|
-
if 'git' not in item.metadata:
|
|
339
|
-
item.metadata['git'] = dict()
|
|
340
|
-
|
|
341
|
-
# add to metadata
|
|
342
|
-
item.metadata['git']['status'] = miscellaneous.GitUtils.git_status(path=directory)
|
|
343
|
-
item.metadata['git']['log'] = miscellaneous.GitUtils.git_log(path=directory)
|
|
344
|
-
item.metadata['git']['url'] = miscellaneous.GitUtils.git_url(path=directory)
|
|
345
|
-
|
|
346
|
-
# update item
|
|
347
|
-
item = self.items_repository.update(item=item, system_metadata=True)
|
|
348
|
-
codebase = entities.Codebase(type='item',
|
|
349
|
-
client_api=self._client_api,
|
|
350
|
-
itemId=item.id,
|
|
351
|
-
item=item)
|
|
352
|
-
except Exception:
|
|
353
|
-
logger.error('Error when packing:')
|
|
354
|
-
raise
|
|
355
|
-
finally:
|
|
356
|
-
# cleanup
|
|
357
|
-
if zip_filename is not None:
|
|
358
|
-
if os.path.isfile(zip_filename):
|
|
359
|
-
os.remove(zip_filename)
|
|
360
|
-
return codebase
|
|
361
|
-
|
|
362
|
-
def _unpack_single(self,
|
|
363
|
-
codebase,
|
|
364
|
-
download_path: str,
|
|
365
|
-
local_path: str):
|
|
366
|
-
"""
|
|
367
|
-
:param dtlpy.entities.codebase.Codebase codebase: codebase object
|
|
368
|
-
:param str download_path:
|
|
369
|
-
:param str local_path:
|
|
370
|
-
"""
|
|
371
|
-
# downloading with specific filename
|
|
372
|
-
if isinstance(codebase, entities.ItemCodebase):
|
|
373
|
-
artifact_filepath = self.items_repository.download(items=codebase.item_id,
|
|
374
|
-
save_locally=True,
|
|
375
|
-
local_path=os.path.join(download_path,
|
|
376
|
-
codebase.item.name),
|
|
377
|
-
to_items_folder=False)
|
|
378
|
-
if not os.path.isfile(artifact_filepath):
|
|
379
|
-
raise PlatformException(error='404',
|
|
380
|
-
message='error downloading codebase. see above for more information')
|
|
381
|
-
miscellaneous.Zipping.unzip_directory(zip_filename=artifact_filepath,
|
|
382
|
-
to_directory=local_path)
|
|
383
|
-
os.remove(artifact_filepath)
|
|
384
|
-
logger.info('Source code was unpacked to: {}'.format(artifact_filepath))
|
|
385
|
-
elif isinstance(codebase, entities.Item):
|
|
386
|
-
artifact_filepath = codebase.download(save_locally=True,
|
|
387
|
-
local_path=os.path.join(download_path,
|
|
388
|
-
codebase.name),
|
|
389
|
-
to_items_folder=False)
|
|
390
|
-
if not os.path.isfile(artifact_filepath):
|
|
391
|
-
raise PlatformException(error='404',
|
|
392
|
-
message='error downloading codebase. see above for more information')
|
|
393
|
-
miscellaneous.Zipping.unzip_directory(zip_filename=artifact_filepath,
|
|
394
|
-
to_directory=local_path)
|
|
395
|
-
os.remove(artifact_filepath)
|
|
396
|
-
logger.info('Source code was unpacked to: {}'.format(artifact_filepath))
|
|
397
|
-
elif isinstance(codebase, entities.GitCodebase):
|
|
398
|
-
if codebase.is_git_repo(local_path) or \
|
|
399
|
-
codebase.is_git_repo(os.path.join(local_path, codebase.git_repo_name)):
|
|
400
|
-
artifact_filepath = self.pull_git(codebase=codebase, local_path=local_path)
|
|
401
|
-
else: # Clone the repo if not exist
|
|
402
|
-
artifact_filepath = self.clone_git(codebase=codebase, local_path=local_path)
|
|
403
|
-
else:
|
|
404
|
-
raise ValueError('Not implemented: "_unpack_single" for codebase type: {!r}'.format(codebase.type))
|
|
405
|
-
return artifact_filepath
|
|
406
|
-
|
|
407
|
-
def _get_single(self, codebase: entities.GitCodebase, name: str):
|
|
408
|
-
value = None
|
|
409
|
-
integration_id = codebase.credentials.get(name, {}).get('id', None)
|
|
410
|
-
key = codebase.credentials.get(name, {}).get('key', None)
|
|
411
|
-
if integration_id is not None:
|
|
412
|
-
try:
|
|
413
|
-
key = self.project.integrations.get(integrations_id=integration_id).name
|
|
414
|
-
except Exception:
|
|
415
|
-
pass
|
|
416
|
-
value = os.environ.get(key, None)
|
|
417
|
-
|
|
418
|
-
return value
|
|
419
|
-
|
|
420
|
-
def _get_credential(self, codebase: entities.GitCodebase):
|
|
421
|
-
username = password = None
|
|
422
|
-
|
|
423
|
-
if codebase.credentials:
|
|
424
|
-
try:
|
|
425
|
-
username = self._get_single(codebase=codebase, name='username')
|
|
426
|
-
password = self._get_single(codebase=codebase, name='password')
|
|
427
|
-
except Exception:
|
|
428
|
-
logger.exception('Failed to get credentials from codebase')
|
|
429
|
-
|
|
430
|
-
return username, password
|
|
431
|
-
|
|
432
|
-
def clone_git(self,
|
|
433
|
-
codebase: entities.Codebase,
|
|
434
|
-
local_path: str):
|
|
435
|
-
"""
|
|
436
|
-
Clone code base
|
|
437
|
-
|
|
438
|
-
**Prerequisites**: You must be in the role of an *owner* or *developer*. You must have a package.
|
|
439
|
-
|
|
440
|
-
:param dtlpy.entities.codebase.Codebase codebase: codebase object
|
|
441
|
-
:param str local_path: local path
|
|
442
|
-
:return: path where the clone will be
|
|
443
|
-
:rtype: str
|
|
444
|
-
|
|
445
|
-
**Example**:
|
|
446
|
-
|
|
447
|
-
.. code-block:: python
|
|
448
|
-
|
|
449
|
-
package.codebases.clone_git(codebase='codebase_entity', local_path='local_path')
|
|
450
|
-
"""
|
|
451
|
-
if not isinstance(codebase, entities.GitCodebase):
|
|
452
|
-
raise RuntimeError('only support Git Codebase')
|
|
453
|
-
username, password = self._get_credential(codebase=codebase)
|
|
454
|
-
response = self.git_utils.git_clone(path=local_path,
|
|
455
|
-
git_url=codebase.git_url,
|
|
456
|
-
tag=codebase.git_tag,
|
|
457
|
-
username=username,
|
|
458
|
-
password=password)
|
|
459
|
-
if response:
|
|
460
|
-
logger.info('Source code was cloned from {}(Git) to: {}'.format(codebase.git_url, local_path))
|
|
461
|
-
else:
|
|
462
|
-
raise RuntimeError('Failed cloning. See above for full log. codebase: {}'.format(codebase))
|
|
463
|
-
return local_path
|
|
464
|
-
|
|
465
|
-
def pull_git(self, codebase, local_path):
|
|
466
|
-
"""
|
|
467
|
-
Pull (download) a codebase.
|
|
468
|
-
|
|
469
|
-
**Prerequisites**: You must be in the role of an *owner* or *developer*. You must have a package.
|
|
470
|
-
|
|
471
|
-
:param dtlpy.entities.codebase.Codebase codebase: codebase object
|
|
472
|
-
:param str local_path: local path
|
|
473
|
-
:return: path where the Pull will be
|
|
474
|
-
:rtype: str
|
|
475
|
-
|
|
476
|
-
**Example**:
|
|
477
|
-
|
|
478
|
-
.. code-block:: python
|
|
479
|
-
|
|
480
|
-
package.codebases.pull_git(codebase='codebase_entity', local_path='local_path')
|
|
481
|
-
"""
|
|
482
|
-
pull_cmd = 'git pull'
|
|
483
|
-
if not codebase.is_git_repo(local_path):
|
|
484
|
-
local_path = os.path.join(local_path, codebase.git_repo_name)
|
|
485
|
-
response = self.git_utils.git_command(path=local_path, cmd=pull_cmd)
|
|
486
|
-
if response:
|
|
487
|
-
logger.info('pull successful {}(Git) to: {}'.format(codebase.git_url, os.path.dirname(local_path)))
|
|
488
|
-
else:
|
|
489
|
-
logger.critical("Could not pull")
|
|
490
|
-
|
|
491
|
-
# we can test if this is not the same repo if needed...
|
|
492
|
-
# FIXME need to change the order - checkout new branch and pull
|
|
493
|
-
response_2 = self.git_utils.git_command(path=local_path, cmd='git checkout {}'.format(codebase.git_tag))
|
|
494
|
-
return local_path
|
|
495
|
-
|
|
496
|
-
def unpack(self,
|
|
497
|
-
codebase: entities.Codebase = None,
|
|
498
|
-
codebase_name: str = None,
|
|
499
|
-
codebase_id: str = None,
|
|
500
|
-
local_path: str = None,
|
|
501
|
-
version: str = None):
|
|
502
|
-
"""
|
|
503
|
-
Unpack codebase locally. Download source code and unzip.
|
|
504
|
-
|
|
505
|
-
**Prerequisites**: You must be in the role of an *owner* or *developer*. You must have a package.
|
|
506
|
-
|
|
507
|
-
:param dtlpy.entities.codebase.Codebase codebase: `dl.Codebase` object
|
|
508
|
-
:param str codebase_name: search by name
|
|
509
|
-
:param str codebase_id: search by id
|
|
510
|
-
:param str local_path: local path to save codebase
|
|
511
|
-
:param str version: codebase version to unpack. default - latest
|
|
512
|
-
:return: String (dirpath)
|
|
513
|
-
:rtype: str
|
|
514
|
-
|
|
515
|
-
**Example**:
|
|
516
|
-
|
|
517
|
-
.. code-block:: python
|
|
518
|
-
|
|
519
|
-
package.codebases.unpack(codebase='codebase_entity', local_path='local_path')
|
|
520
|
-
"""
|
|
521
|
-
# get the codebase / multiple codebase
|
|
522
|
-
if codebase is None:
|
|
523
|
-
codebase = self.get(codebase_name=codebase_name,
|
|
524
|
-
codebase_id=codebase_id,
|
|
525
|
-
version=version)
|
|
526
|
-
elif codebase_name is not None or codebase_id is not None:
|
|
527
|
-
logger.warning("Using given codebase. Does not preforming search with name {!r} / id {!r}".
|
|
528
|
-
format(codebase_name, codebase_id))
|
|
529
|
-
download_path = local_path
|
|
530
|
-
if isinstance(codebase, entities.PagedEntities):
|
|
531
|
-
for page in codebase:
|
|
532
|
-
for item in page:
|
|
533
|
-
local_path = os.path.join(download_path, 'v.' + item.name.split('.')[0])
|
|
534
|
-
self._unpack_single(codebase=item,
|
|
535
|
-
download_path=download_path,
|
|
536
|
-
local_path=local_path)
|
|
537
|
-
return os.path.dirname(local_path)
|
|
538
|
-
elif isinstance(codebase, list):
|
|
539
|
-
for item in codebase:
|
|
540
|
-
local_path = os.path.join(download_path, 'v.' + item.item.name.split('.')[0])
|
|
541
|
-
self._unpack_single(codebase=item,
|
|
542
|
-
download_path=download_path,
|
|
543
|
-
local_path=local_path)
|
|
544
|
-
return os.path.dirname(local_path)
|
|
545
|
-
elif isinstance(codebase, (entities.ItemCodebase, entities.Item, entities.GitCodebase)):
|
|
546
|
-
artifact_filepath = self._unpack_single(codebase=codebase,
|
|
547
|
-
download_path=download_path,
|
|
548
|
-
local_path=local_path)
|
|
549
|
-
if isinstance(codebase, (entities.ItemCodebase, entities.Item)):
|
|
550
|
-
dir_path = os.path.dirname(artifact_filepath) # use the directory of the artifact
|
|
551
|
-
else:
|
|
552
|
-
dir_path = artifact_filepath
|
|
553
|
-
logger.info('Source code was unpacked to: {}'.format(dir_path))
|
|
554
|
-
else:
|
|
555
|
-
raise PlatformException(
|
|
556
|
-
error='404',
|
|
557
|
-
message='Codebase was not found! name:{name}, id:{id}'.format(name=codebase_name,
|
|
558
|
-
id=codebase_id))
|
|
559
|
-
return dir_path
|
|
1
|
+
import hashlib
|
|
2
|
+
import logging
|
|
3
|
+
import os
|
|
4
|
+
import io
|
|
5
|
+
import random
|
|
6
|
+
from typing import List
|
|
7
|
+
|
|
8
|
+
from .. import entities, PlatformException, exceptions, repositories, miscellaneous
|
|
9
|
+
from ..services.api_client import ApiClient
|
|
10
|
+
from ..services.api_client import client as client_api
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(name='dtlpy')
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Codebases:
|
|
16
|
+
"""
|
|
17
|
+
Codebase Repository
|
|
18
|
+
|
|
19
|
+
The Codebases class allows the user to manage codebases and their properties.
|
|
20
|
+
The codebase is the code the user uploads for the user's packages to run.
|
|
21
|
+
Read more about `codebase <https://developers.dataloop.ai/tutorials/faas/introduction/chapter/>`_ in our FaaS (function as a service).
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def __init__(self,
|
|
25
|
+
client_api: ApiClient,
|
|
26
|
+
project: entities.Project = None,
|
|
27
|
+
dataset: entities.Dataset = None,
|
|
28
|
+
project_id: str = None):
|
|
29
|
+
self._client_api = client_api
|
|
30
|
+
self._project = project
|
|
31
|
+
self._project_id = project_id
|
|
32
|
+
self._dataset = dataset
|
|
33
|
+
self._items_repository = None
|
|
34
|
+
self.git_utils = miscellaneous.GitUtils()
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def items_repository(self) -> repositories.Items:
|
|
38
|
+
if self._items_repository is None:
|
|
39
|
+
if self._dataset is not None or self._project is not None:
|
|
40
|
+
self._items_repository = self.dataset.items
|
|
41
|
+
else:
|
|
42
|
+
self._items_repository = repositories.Items(client_api=client_api)
|
|
43
|
+
assert isinstance(self._items_repository, repositories.Items)
|
|
44
|
+
return self._items_repository
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
def project(self) -> entities.Project:
|
|
48
|
+
if self._project is None:
|
|
49
|
+
# project is None - try dataset
|
|
50
|
+
if self._dataset is None:
|
|
51
|
+
# dataset is None - try from project id
|
|
52
|
+
if self._project_id is None:
|
|
53
|
+
raise PlatformException(error='400',
|
|
54
|
+
message='Cannot perform this action without a project')
|
|
55
|
+
else:
|
|
56
|
+
self._project = repositories.Projects(client_api=self._client_api).get(project_id=self._project_id)
|
|
57
|
+
else:
|
|
58
|
+
# dataset is not None - take project from there
|
|
59
|
+
self._project = self.dataset.project
|
|
60
|
+
assert isinstance(self._project, entities.Project)
|
|
61
|
+
return self._project
|
|
62
|
+
|
|
63
|
+
@property
|
|
64
|
+
def dataset(self) -> entities.Dataset:
|
|
65
|
+
if self._dataset is None:
|
|
66
|
+
# get dataset from project
|
|
67
|
+
try:
|
|
68
|
+
self._dataset = self.project.datasets._get_binaries_dataset()
|
|
69
|
+
except exceptions.NotFound:
|
|
70
|
+
raise ValueError('Missing "Binaries" dataset in the project. Please contact support for help')
|
|
71
|
+
assert isinstance(self._dataset, entities.Dataset)
|
|
72
|
+
return self._dataset
|
|
73
|
+
|
|
74
|
+
@dataset.setter
|
|
75
|
+
def dataset(self, dataset: entities.Dataset):
|
|
76
|
+
if not isinstance(dataset, entities.Dataset):
|
|
77
|
+
raise ValueError('Must input a valid Dataset entity')
|
|
78
|
+
self._dataset = dataset
|
|
79
|
+
|
|
80
|
+
@staticmethod
|
|
81
|
+
def __file_hash(filepath):
|
|
82
|
+
m = hashlib.md5()
|
|
83
|
+
with open(filepath, 'rb') as f:
|
|
84
|
+
for chunk in iter(lambda: f.read(4096), b''):
|
|
85
|
+
m.update(chunk)
|
|
86
|
+
return m.hexdigest()
|
|
87
|
+
|
|
88
|
+
def list_versions(self, codebase_name: str) -> entities.PagedEntities:
|
|
89
|
+
"""
|
|
90
|
+
List all codebase versions.
|
|
91
|
+
|
|
92
|
+
**Prerequisites**: You must be in the role of an *owner* or *developer*. You must have a package.
|
|
93
|
+
|
|
94
|
+
**Example**:
|
|
95
|
+
|
|
96
|
+
.. code-block:: python
|
|
97
|
+
|
|
98
|
+
package.codebases.list_versions(codebase_name='codebase_name')
|
|
99
|
+
|
|
100
|
+
:param str codebase_name: code base name
|
|
101
|
+
:return: list of versions
|
|
102
|
+
:rtype: list
|
|
103
|
+
"""
|
|
104
|
+
filters = entities.Filters()
|
|
105
|
+
filters.add(field='filename', values='/codebases/{}/*'.format(codebase_name))
|
|
106
|
+
versions = self.items_repository.list(filters=filters)
|
|
107
|
+
return versions
|
|
108
|
+
|
|
109
|
+
def list(self) -> entities.PagedEntities:
|
|
110
|
+
"""
|
|
111
|
+
List all codebases.
|
|
112
|
+
|
|
113
|
+
**Prerequisites**: You must be in the role of an *owner* or *developer*. You must have a package.
|
|
114
|
+
|
|
115
|
+
**Example**:
|
|
116
|
+
|
|
117
|
+
.. code-block:: python
|
|
118
|
+
|
|
119
|
+
package.codebases.list()
|
|
120
|
+
|
|
121
|
+
:return: Paged entity
|
|
122
|
+
:rtype: dtlpy.entities.paged_entities.PagedEntities
|
|
123
|
+
"""
|
|
124
|
+
filters = entities.Filters()
|
|
125
|
+
filters.add(field='filename', values='/codebases/*')
|
|
126
|
+
filters.add(field='type', values='dir')
|
|
127
|
+
codebases = self.items_repository.list(filters=filters)
|
|
128
|
+
return codebases
|
|
129
|
+
|
|
130
|
+
def get(self,
|
|
131
|
+
codebase_name: str = None,
|
|
132
|
+
codebase_id: str = None,
|
|
133
|
+
version: str = None):
|
|
134
|
+
"""
|
|
135
|
+
Get a Codebase object to use in your code.
|
|
136
|
+
|
|
137
|
+
**Prerequisites**: You must be in the role of an *owner* or *developer*. You must have a package.
|
|
138
|
+
|
|
139
|
+
**Example**:
|
|
140
|
+
|
|
141
|
+
.. code-block:: python
|
|
142
|
+
|
|
143
|
+
package.codebases.get(codebase_name='codebase_name')
|
|
144
|
+
|
|
145
|
+
:param str codebase_name: optional - search by name
|
|
146
|
+
:param str codebase_id: optional - search by id
|
|
147
|
+
:param str version: codebase version. default is latest. options: "all", "latest" or ver number - "10"
|
|
148
|
+
:return: Codebase object
|
|
149
|
+
"""
|
|
150
|
+
if codebase_id is not None:
|
|
151
|
+
matched_version = self.items_repository.get(item_id=codebase_id)
|
|
152
|
+
# verify input codebase name is same as the given id
|
|
153
|
+
if codebase_name is not None and matched_version.name != codebase_name:
|
|
154
|
+
logger.warning(
|
|
155
|
+
"Mismatch found in codebases.get: codebase_name is different then codebase.name: "
|
|
156
|
+
"{!r} != {!r}".format(
|
|
157
|
+
codebase_name,
|
|
158
|
+
matched_version.name))
|
|
159
|
+
codebase = entities.Codebase(type='item',
|
|
160
|
+
client_api=self._client_api,
|
|
161
|
+
itemId=matched_version.id,
|
|
162
|
+
item=matched_version)
|
|
163
|
+
return codebase
|
|
164
|
+
|
|
165
|
+
if codebase_name is None:
|
|
166
|
+
raise PlatformException(error='400', message='Either "codebase_name" or "codebase_id" is needed')
|
|
167
|
+
if version is None:
|
|
168
|
+
version = 'latest'
|
|
169
|
+
|
|
170
|
+
if version not in ['all', 'latest']:
|
|
171
|
+
try:
|
|
172
|
+
matched_version = self.items_repository.get(
|
|
173
|
+
filepath='/codebases/{}/{}.zip'.format(codebase_name, version))
|
|
174
|
+
except Exception:
|
|
175
|
+
raise PlatformException(error='404',
|
|
176
|
+
message='No matching version was found. version: {}'.format(version))
|
|
177
|
+
codebase = entities.Codebase(type='item',
|
|
178
|
+
client_api=self._client_api,
|
|
179
|
+
itemId=matched_version.id,
|
|
180
|
+
item=matched_version)
|
|
181
|
+
return codebase
|
|
182
|
+
|
|
183
|
+
# get all or latest
|
|
184
|
+
versions_pages = self.list_versions(codebase_name=codebase_name)
|
|
185
|
+
if versions_pages.items_count == 0:
|
|
186
|
+
raise PlatformException(error='404', message='No codebase was found. name: {}'.format(codebase_name))
|
|
187
|
+
else:
|
|
188
|
+
if version == 'all':
|
|
189
|
+
codebase = [entities.Codebase(type='item',
|
|
190
|
+
client_api=self._client_api,
|
|
191
|
+
itemId=mv.id,
|
|
192
|
+
item=mv) for mv in versions_pages.all()]
|
|
193
|
+
elif version == 'latest':
|
|
194
|
+
max_ver = -1
|
|
195
|
+
matched_version = None
|
|
196
|
+
for page in versions_pages:
|
|
197
|
+
for ver in page:
|
|
198
|
+
if ver.type == 'dir':
|
|
199
|
+
continue
|
|
200
|
+
# extract version from filepath
|
|
201
|
+
ver_int = int(os.path.splitext(ver.name)[0])
|
|
202
|
+
if ver_int > max_ver:
|
|
203
|
+
max_ver = ver_int
|
|
204
|
+
matched_version = ver
|
|
205
|
+
if matched_version is None:
|
|
206
|
+
raise PlatformException(error='404',
|
|
207
|
+
message='No codebase was found. name: {}'.format(codebase_name))
|
|
208
|
+
else:
|
|
209
|
+
codebase = entities.Codebase(type='item',
|
|
210
|
+
client_api=self._client_api,
|
|
211
|
+
itemId=matched_version.id,
|
|
212
|
+
item=matched_version)
|
|
213
|
+
else:
|
|
214
|
+
raise PlatformException(error='404', message='Unknown version string: {}'.format(version))
|
|
215
|
+
|
|
216
|
+
return codebase
|
|
217
|
+
|
|
218
|
+
@staticmethod
|
|
219
|
+
def get_current_version(all_versions_pages, zip_md):
|
|
220
|
+
"""
|
|
221
|
+
This method returns the current version of the codebase and other versions found.
|
|
222
|
+
|
|
223
|
+
**Prerequisites**: You must be in the role of an *owner* or *developer*. You must have a package.
|
|
224
|
+
|
|
225
|
+
:param codebase all_versions_pages: codebase object
|
|
226
|
+
:param zip_md: zipped file of codebase
|
|
227
|
+
:return: current version and all versions found of codebase
|
|
228
|
+
:rtype: int, int
|
|
229
|
+
|
|
230
|
+
**Example**:
|
|
231
|
+
|
|
232
|
+
.. code-block:: python
|
|
233
|
+
|
|
234
|
+
package.codebases.get_current_version(all_versions_pages='codebase_entity', zip_md='path')
|
|
235
|
+
"""
|
|
236
|
+
latest_version = 0
|
|
237
|
+
same_version_found = None
|
|
238
|
+
# go over all existing versions
|
|
239
|
+
for v_item in all_versions_pages:
|
|
240
|
+
# get latest version
|
|
241
|
+
if int(os.path.splitext(v_item.item.name)[0]) > latest_version:
|
|
242
|
+
latest_version = int(os.path.splitext(v_item.item.name)[0])
|
|
243
|
+
# check md5 to find same codebase
|
|
244
|
+
if 'md5' in v_item.item.metadata['system'] and v_item.item.metadata['system']['md5'] == zip_md:
|
|
245
|
+
same_version_found = v_item
|
|
246
|
+
break
|
|
247
|
+
return latest_version + 1, same_version_found
|
|
248
|
+
|
|
249
|
+
def pack(self,
|
|
250
|
+
directory: str,
|
|
251
|
+
name: str = None,
|
|
252
|
+
extension: str = 'zip',
|
|
253
|
+
description: str = '',
|
|
254
|
+
ignore_directories: List[str] = None,
|
|
255
|
+
ignore_max_file_size: bool = False):
|
|
256
|
+
"""
|
|
257
|
+
Zip a local code directory and post to codebases.
|
|
258
|
+
|
|
259
|
+
**Prerequisites**: You must be in the role of an *owner* or *developer*. You must have a package.
|
|
260
|
+
|
|
261
|
+
:param str directory: local directory to pack
|
|
262
|
+
:param str name: codebase name
|
|
263
|
+
:param str extension: the extension of the file
|
|
264
|
+
:param str description: codebase description
|
|
265
|
+
:param list[str] ignore_directories: directories to ignore.
|
|
266
|
+
:return: Codebase object
|
|
267
|
+
:rtype: dtlpy.entities.codebase.Codebase
|
|
268
|
+
|
|
269
|
+
**Example**:
|
|
270
|
+
|
|
271
|
+
.. code-block:: python
|
|
272
|
+
|
|
273
|
+
package.codebases.pack(directory='path_dir', name='codebase_name')
|
|
274
|
+
"""
|
|
275
|
+
# create/get .dataloop dir
|
|
276
|
+
cwd = os.getcwd()
|
|
277
|
+
dl_dir = os.path.join(cwd, '.dataloop')
|
|
278
|
+
if not os.path.isdir(dl_dir):
|
|
279
|
+
os.mkdir(dl_dir)
|
|
280
|
+
|
|
281
|
+
# get codebase name
|
|
282
|
+
if name is None:
|
|
283
|
+
name = os.path.basename(directory)
|
|
284
|
+
|
|
285
|
+
# create/get dist folder
|
|
286
|
+
zip_filename = os.path.join(dl_dir, '{}_{}.{}'.format(name, str(random.randrange(0, 1000)), extension))
|
|
287
|
+
|
|
288
|
+
try:
|
|
289
|
+
if not os.path.isdir(directory):
|
|
290
|
+
raise PlatformException(error='400', message='Not a directory: {}'.format(directory))
|
|
291
|
+
directory = os.path.abspath(directory)
|
|
292
|
+
|
|
293
|
+
# create zipfile
|
|
294
|
+
miscellaneous.Zipping.zip_directory(zip_filename=zip_filename,
|
|
295
|
+
directory=directory,
|
|
296
|
+
ignore_directories=ignore_directories,
|
|
297
|
+
ignore_max_file_size=ignore_max_file_size)
|
|
298
|
+
zip_md = self.__file_hash(zip_filename)
|
|
299
|
+
|
|
300
|
+
# get latest version
|
|
301
|
+
same_version_found = None
|
|
302
|
+
try:
|
|
303
|
+
all_versions_pages = self.get(codebase_name=name, version='all')
|
|
304
|
+
except exceptions.NotFound:
|
|
305
|
+
all_versions_pages = None
|
|
306
|
+
if all_versions_pages is None:
|
|
307
|
+
# no codebase with that name - create new version
|
|
308
|
+
current_version = 0
|
|
309
|
+
else:
|
|
310
|
+
current_version, same_version_found = self.get_current_version(all_versions_pages=all_versions_pages,
|
|
311
|
+
zip_md=zip_md)
|
|
312
|
+
|
|
313
|
+
if same_version_found is not None:
|
|
314
|
+
# same md5 hash file found in version - return the matched version
|
|
315
|
+
codebase = same_version_found
|
|
316
|
+
else:
|
|
317
|
+
# no matched version was found - create a new version
|
|
318
|
+
# read from zipped file
|
|
319
|
+
with open(zip_filename, 'rb') as f:
|
|
320
|
+
buffer = io.BytesIO(f.read())
|
|
321
|
+
buffer.name = '{}.{}'.format(str(current_version), extension)
|
|
322
|
+
|
|
323
|
+
# upload item
|
|
324
|
+
item = self.items_repository.upload(local_path=buffer,
|
|
325
|
+
remote_path='/codebases/{}'.format(name))
|
|
326
|
+
if isinstance(item, list) and len(item) == 0:
|
|
327
|
+
raise PlatformException(error='400', message='Failed upload codebase, check log file for details')
|
|
328
|
+
|
|
329
|
+
# add source code to metadata
|
|
330
|
+
if 'system' not in item.metadata:
|
|
331
|
+
item.metadata['system'] = dict()
|
|
332
|
+
item.metadata['system']['description'] = description
|
|
333
|
+
item.metadata['system']['md5'] = zip_md
|
|
334
|
+
|
|
335
|
+
# add git info to metadata
|
|
336
|
+
if miscellaneous.GitUtils.is_git_repo(path=directory):
|
|
337
|
+
# create 'git' field in metadata
|
|
338
|
+
if 'git' not in item.metadata:
|
|
339
|
+
item.metadata['git'] = dict()
|
|
340
|
+
|
|
341
|
+
# add to metadata
|
|
342
|
+
item.metadata['git']['status'] = miscellaneous.GitUtils.git_status(path=directory)
|
|
343
|
+
item.metadata['git']['log'] = miscellaneous.GitUtils.git_log(path=directory)
|
|
344
|
+
item.metadata['git']['url'] = miscellaneous.GitUtils.git_url(path=directory)
|
|
345
|
+
|
|
346
|
+
# update item
|
|
347
|
+
item = self.items_repository.update(item=item, system_metadata=True)
|
|
348
|
+
codebase = entities.Codebase(type='item',
|
|
349
|
+
client_api=self._client_api,
|
|
350
|
+
itemId=item.id,
|
|
351
|
+
item=item)
|
|
352
|
+
except Exception:
|
|
353
|
+
logger.error('Error when packing:')
|
|
354
|
+
raise
|
|
355
|
+
finally:
|
|
356
|
+
# cleanup
|
|
357
|
+
if zip_filename is not None:
|
|
358
|
+
if os.path.isfile(zip_filename):
|
|
359
|
+
os.remove(zip_filename)
|
|
360
|
+
return codebase
|
|
361
|
+
|
|
362
|
+
def _unpack_single(self,
|
|
363
|
+
codebase,
|
|
364
|
+
download_path: str,
|
|
365
|
+
local_path: str):
|
|
366
|
+
"""
|
|
367
|
+
:param dtlpy.entities.codebase.Codebase codebase: codebase object
|
|
368
|
+
:param str download_path:
|
|
369
|
+
:param str local_path:
|
|
370
|
+
"""
|
|
371
|
+
# downloading with specific filename
|
|
372
|
+
if isinstance(codebase, entities.ItemCodebase):
|
|
373
|
+
artifact_filepath = self.items_repository.download(items=codebase.item_id,
|
|
374
|
+
save_locally=True,
|
|
375
|
+
local_path=os.path.join(download_path,
|
|
376
|
+
codebase.item.name),
|
|
377
|
+
to_items_folder=False)
|
|
378
|
+
if not os.path.isfile(artifact_filepath):
|
|
379
|
+
raise PlatformException(error='404',
|
|
380
|
+
message='error downloading codebase. see above for more information')
|
|
381
|
+
miscellaneous.Zipping.unzip_directory(zip_filename=artifact_filepath,
|
|
382
|
+
to_directory=local_path)
|
|
383
|
+
os.remove(artifact_filepath)
|
|
384
|
+
logger.info('Source code was unpacked to: {}'.format(artifact_filepath))
|
|
385
|
+
elif isinstance(codebase, entities.Item):
|
|
386
|
+
artifact_filepath = codebase.download(save_locally=True,
|
|
387
|
+
local_path=os.path.join(download_path,
|
|
388
|
+
codebase.name),
|
|
389
|
+
to_items_folder=False)
|
|
390
|
+
if not os.path.isfile(artifact_filepath):
|
|
391
|
+
raise PlatformException(error='404',
|
|
392
|
+
message='error downloading codebase. see above for more information')
|
|
393
|
+
miscellaneous.Zipping.unzip_directory(zip_filename=artifact_filepath,
|
|
394
|
+
to_directory=local_path)
|
|
395
|
+
os.remove(artifact_filepath)
|
|
396
|
+
logger.info('Source code was unpacked to: {}'.format(artifact_filepath))
|
|
397
|
+
elif isinstance(codebase, entities.GitCodebase):
|
|
398
|
+
if codebase.is_git_repo(local_path) or \
|
|
399
|
+
codebase.is_git_repo(os.path.join(local_path, codebase.git_repo_name)):
|
|
400
|
+
artifact_filepath = self.pull_git(codebase=codebase, local_path=local_path)
|
|
401
|
+
else: # Clone the repo if not exist
|
|
402
|
+
artifact_filepath = self.clone_git(codebase=codebase, local_path=local_path)
|
|
403
|
+
else:
|
|
404
|
+
raise ValueError('Not implemented: "_unpack_single" for codebase type: {!r}'.format(codebase.type))
|
|
405
|
+
return artifact_filepath
|
|
406
|
+
|
|
407
|
+
def _get_single(self, codebase: entities.GitCodebase, name: str):
|
|
408
|
+
value = None
|
|
409
|
+
integration_id = codebase.credentials.get(name, {}).get('id', None)
|
|
410
|
+
key = codebase.credentials.get(name, {}).get('key', None)
|
|
411
|
+
if integration_id is not None:
|
|
412
|
+
try:
|
|
413
|
+
key = self.project.integrations.get(integrations_id=integration_id).name
|
|
414
|
+
except Exception:
|
|
415
|
+
pass
|
|
416
|
+
value = os.environ.get(key, None)
|
|
417
|
+
|
|
418
|
+
return value
|
|
419
|
+
|
|
420
|
+
def _get_credential(self, codebase: entities.GitCodebase):
|
|
421
|
+
username = password = None
|
|
422
|
+
|
|
423
|
+
if codebase.credentials:
|
|
424
|
+
try:
|
|
425
|
+
username = self._get_single(codebase=codebase, name='username')
|
|
426
|
+
password = self._get_single(codebase=codebase, name='password')
|
|
427
|
+
except Exception:
|
|
428
|
+
logger.exception('Failed to get credentials from codebase')
|
|
429
|
+
|
|
430
|
+
return username, password
|
|
431
|
+
|
|
432
|
+
def clone_git(self,
|
|
433
|
+
codebase: entities.Codebase,
|
|
434
|
+
local_path: str):
|
|
435
|
+
"""
|
|
436
|
+
Clone code base
|
|
437
|
+
|
|
438
|
+
**Prerequisites**: You must be in the role of an *owner* or *developer*. You must have a package.
|
|
439
|
+
|
|
440
|
+
:param dtlpy.entities.codebase.Codebase codebase: codebase object
|
|
441
|
+
:param str local_path: local path
|
|
442
|
+
:return: path where the clone will be
|
|
443
|
+
:rtype: str
|
|
444
|
+
|
|
445
|
+
**Example**:
|
|
446
|
+
|
|
447
|
+
.. code-block:: python
|
|
448
|
+
|
|
449
|
+
package.codebases.clone_git(codebase='codebase_entity', local_path='local_path')
|
|
450
|
+
"""
|
|
451
|
+
if not isinstance(codebase, entities.GitCodebase):
|
|
452
|
+
raise RuntimeError('only support Git Codebase')
|
|
453
|
+
username, password = self._get_credential(codebase=codebase)
|
|
454
|
+
response = self.git_utils.git_clone(path=local_path,
|
|
455
|
+
git_url=codebase.git_url,
|
|
456
|
+
tag=codebase.git_tag,
|
|
457
|
+
username=username,
|
|
458
|
+
password=password)
|
|
459
|
+
if response:
|
|
460
|
+
logger.info('Source code was cloned from {}(Git) to: {}'.format(codebase.git_url, local_path))
|
|
461
|
+
else:
|
|
462
|
+
raise RuntimeError('Failed cloning. See above for full log. codebase: {}'.format(codebase))
|
|
463
|
+
return local_path
|
|
464
|
+
|
|
465
|
+
def pull_git(self, codebase, local_path):
|
|
466
|
+
"""
|
|
467
|
+
Pull (download) a codebase.
|
|
468
|
+
|
|
469
|
+
**Prerequisites**: You must be in the role of an *owner* or *developer*. You must have a package.
|
|
470
|
+
|
|
471
|
+
:param dtlpy.entities.codebase.Codebase codebase: codebase object
|
|
472
|
+
:param str local_path: local path
|
|
473
|
+
:return: path where the Pull will be
|
|
474
|
+
:rtype: str
|
|
475
|
+
|
|
476
|
+
**Example**:
|
|
477
|
+
|
|
478
|
+
.. code-block:: python
|
|
479
|
+
|
|
480
|
+
package.codebases.pull_git(codebase='codebase_entity', local_path='local_path')
|
|
481
|
+
"""
|
|
482
|
+
pull_cmd = 'git pull'
|
|
483
|
+
if not codebase.is_git_repo(local_path):
|
|
484
|
+
local_path = os.path.join(local_path, codebase.git_repo_name)
|
|
485
|
+
response = self.git_utils.git_command(path=local_path, cmd=pull_cmd)
|
|
486
|
+
if response:
|
|
487
|
+
logger.info('pull successful {}(Git) to: {}'.format(codebase.git_url, os.path.dirname(local_path)))
|
|
488
|
+
else:
|
|
489
|
+
logger.critical("Could not pull")
|
|
490
|
+
|
|
491
|
+
# we can test if this is not the same repo if needed...
|
|
492
|
+
# FIXME need to change the order - checkout new branch and pull
|
|
493
|
+
response_2 = self.git_utils.git_command(path=local_path, cmd='git checkout {}'.format(codebase.git_tag))
|
|
494
|
+
return local_path
|
|
495
|
+
|
|
496
|
+
def unpack(self,
|
|
497
|
+
codebase: entities.Codebase = None,
|
|
498
|
+
codebase_name: str = None,
|
|
499
|
+
codebase_id: str = None,
|
|
500
|
+
local_path: str = None,
|
|
501
|
+
version: str = None):
|
|
502
|
+
"""
|
|
503
|
+
Unpack codebase locally. Download source code and unzip.
|
|
504
|
+
|
|
505
|
+
**Prerequisites**: You must be in the role of an *owner* or *developer*. You must have a package.
|
|
506
|
+
|
|
507
|
+
:param dtlpy.entities.codebase.Codebase codebase: `dl.Codebase` object
|
|
508
|
+
:param str codebase_name: search by name
|
|
509
|
+
:param str codebase_id: search by id
|
|
510
|
+
:param str local_path: local path to save codebase
|
|
511
|
+
:param str version: codebase version to unpack. default - latest
|
|
512
|
+
:return: String (dirpath)
|
|
513
|
+
:rtype: str
|
|
514
|
+
|
|
515
|
+
**Example**:
|
|
516
|
+
|
|
517
|
+
.. code-block:: python
|
|
518
|
+
|
|
519
|
+
package.codebases.unpack(codebase='codebase_entity', local_path='local_path')
|
|
520
|
+
"""
|
|
521
|
+
# get the codebase / multiple codebase
|
|
522
|
+
if codebase is None:
|
|
523
|
+
codebase = self.get(codebase_name=codebase_name,
|
|
524
|
+
codebase_id=codebase_id,
|
|
525
|
+
version=version)
|
|
526
|
+
elif codebase_name is not None or codebase_id is not None:
|
|
527
|
+
logger.warning("Using given codebase. Does not preforming search with name {!r} / id {!r}".
|
|
528
|
+
format(codebase_name, codebase_id))
|
|
529
|
+
download_path = local_path
|
|
530
|
+
if isinstance(codebase, entities.PagedEntities):
|
|
531
|
+
for page in codebase:
|
|
532
|
+
for item in page:
|
|
533
|
+
local_path = os.path.join(download_path, 'v.' + item.name.split('.')[0])
|
|
534
|
+
self._unpack_single(codebase=item,
|
|
535
|
+
download_path=download_path,
|
|
536
|
+
local_path=local_path)
|
|
537
|
+
return os.path.dirname(local_path)
|
|
538
|
+
elif isinstance(codebase, list):
|
|
539
|
+
for item in codebase:
|
|
540
|
+
local_path = os.path.join(download_path, 'v.' + item.item.name.split('.')[0])
|
|
541
|
+
self._unpack_single(codebase=item,
|
|
542
|
+
download_path=download_path,
|
|
543
|
+
local_path=local_path)
|
|
544
|
+
return os.path.dirname(local_path)
|
|
545
|
+
elif isinstance(codebase, (entities.ItemCodebase, entities.Item, entities.GitCodebase)):
|
|
546
|
+
artifact_filepath = self._unpack_single(codebase=codebase,
|
|
547
|
+
download_path=download_path,
|
|
548
|
+
local_path=local_path)
|
|
549
|
+
if isinstance(codebase, (entities.ItemCodebase, entities.Item)):
|
|
550
|
+
dir_path = os.path.dirname(artifact_filepath) # use the directory of the artifact
|
|
551
|
+
else:
|
|
552
|
+
dir_path = artifact_filepath
|
|
553
|
+
logger.info('Source code was unpacked to: {}'.format(dir_path))
|
|
554
|
+
else:
|
|
555
|
+
raise PlatformException(
|
|
556
|
+
error='404',
|
|
557
|
+
message='Codebase was not found! name:{name}, id:{id}'.format(name=codebase_name,
|
|
558
|
+
id=codebase_id))
|
|
559
|
+
return dir_path
|