amsdal 0.4.12__cp312-cp312-macosx_10_13_universal2.whl → 0.5.0__cp312-cp312-macosx_10_13_universal2.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.
- amsdal/__about__.py +1 -1
- amsdal/__migrations__/0000_initial.py +24 -205
- amsdal/__migrations__/0001_create_class_file.py +160 -0
- amsdal/cloud/__init__.cpython-312-darwin.so +0 -0
- amsdal/cloud/client.cpython-312-darwin.so +0 -0
- amsdal/cloud/constants.cpython-312-darwin.so +0 -0
- amsdal/cloud/enums.cpython-312-darwin.so +0 -0
- amsdal/cloud/models/__init__.cpython-312-darwin.so +0 -0
- amsdal/cloud/models/base.cpython-312-darwin.so +0 -0
- amsdal/cloud/services/__init__.cpython-312-darwin.so +0 -0
- amsdal/cloud/services/actions/__init__.cpython-312-darwin.so +0 -0
- amsdal/cloud/services/actions/add_allowlist_ip.cpython-312-darwin.so +0 -0
- amsdal/cloud/services/actions/add_basic_auth.cpython-312-darwin.so +0 -0
- amsdal/cloud/services/actions/add_dependency.cpython-312-darwin.so +0 -0
- amsdal/cloud/services/actions/add_secret.cpython-312-darwin.so +0 -0
- amsdal/cloud/services/actions/base.cpython-312-darwin.so +0 -0
- amsdal/cloud/services/actions/create_deploy.cpython-312-darwin.so +0 -0
- amsdal/cloud/services/actions/create_env.cpython-312-darwin.so +0 -0
- amsdal/cloud/services/actions/create_session.cpython-312-darwin.so +0 -0
- amsdal/cloud/services/actions/delete_allowlist_ip.cpython-312-darwin.so +0 -0
- amsdal/cloud/services/actions/delete_basic_auth.cpython-312-darwin.so +0 -0
- amsdal/cloud/services/actions/delete_dependency.cpython-312-darwin.so +0 -0
- amsdal/cloud/services/actions/delete_env.cpython-312-darwin.so +0 -0
- amsdal/cloud/services/actions/delete_secret.cpython-312-darwin.so +0 -0
- amsdal/cloud/services/actions/destroy_deploy.cpython-312-darwin.so +0 -0
- amsdal/cloud/services/actions/expose_db.cpython-312-darwin.so +0 -0
- amsdal/cloud/services/actions/get_basic_auth_credentials.cpython-312-darwin.so +0 -0
- amsdal/cloud/services/actions/get_monitoring_info.cpython-312-darwin.so +0 -0
- amsdal/cloud/services/actions/list_dependencies.cpython-312-darwin.so +0 -0
- amsdal/cloud/services/actions/list_deploys.cpython-312-darwin.so +0 -0
- amsdal/cloud/services/actions/list_envs.cpython-312-darwin.so +0 -0
- amsdal/cloud/services/actions/list_secrets.cpython-312-darwin.so +0 -0
- amsdal/cloud/services/actions/manager.cpython-312-darwin.so +0 -0
- amsdal/cloud/services/actions/signup_action.cpython-312-darwin.so +0 -0
- amsdal/cloud/services/actions/update_deploy.cpython-312-darwin.so +0 -0
- amsdal/cloud/services/auth/__init__.cpython-312-darwin.so +0 -0
- amsdal/cloud/services/auth/base.cpython-312-darwin.so +0 -0
- amsdal/cloud/services/auth/credentials.cpython-312-darwin.so +0 -0
- amsdal/cloud/services/auth/manager.cpython-312-darwin.so +0 -0
- amsdal/cloud/services/auth/signup_service.cpython-312-darwin.so +0 -0
- amsdal/cloud/services/auth/token.cpython-312-darwin.so +0 -0
- amsdal/contrib/__init__.cpython-312-darwin.so +0 -0
- amsdal/contrib/auth/migrations/0000_initial.py +55 -52
- amsdal/contrib/frontend_configs/migrations/0000_initial.py +154 -183
- amsdal/fixtures/__init__.cpython-312-darwin.so +0 -0
- amsdal/fixtures/manager.cpython-312-darwin.so +0 -0
- amsdal/fixtures/utils.cpython-312-darwin.so +0 -0
- amsdal/manager.cpython-312-darwin.so +0 -0
- amsdal/mixins/__init__.cpython-312-darwin.so +0 -0
- amsdal/mixins/class_versions_mixin.cpython-312-darwin.so +0 -0
- amsdal/models/core/class_object.py +7 -6
- amsdal/models/core/class_object.pyi +6 -5
- amsdal/models/core/class_property.py +6 -1
- amsdal/models/core/class_property.pyi +5 -1
- amsdal/models/core/storage_metadata.py +15 -0
- amsdal/models/core/storage_metadata.pyi +11 -0
- amsdal/models/types/object.py +3 -3
- amsdal/models/types/object.pyi +3 -3
- amsdal/schemas/core/class_object/model.json +20 -0
- amsdal/schemas/core/class_property/model.json +19 -0
- amsdal/schemas/core/storage_metadata/model.json +52 -0
- amsdal/schemas/manager.cpython-312-darwin.so +0 -0
- amsdal/services/__init__.cpython-312-darwin.so +0 -0
- amsdal/services/transaction_execution.cpython-312-darwin.so +0 -0
- amsdal/utils/tests/helpers.py +8 -8
- {amsdal-0.4.12.dist-info → amsdal-0.5.0.dist-info}/METADATA +4 -4
- {amsdal-0.4.12.dist-info → amsdal-0.5.0.dist-info}/RECORD +70 -75
- {amsdal-0.4.12.dist-info → amsdal-0.5.0.dist-info}/WHEEL +1 -1
- amsdal/__migrations__/0001_datetime_type.py +0 -18
- amsdal/__migrations__/0002_fixture_order.py +0 -44
- amsdal/__migrations__/0003_schema_type_in_class_meta.py +0 -44
- amsdal/models/core/class_object_meta.py +0 -26
- amsdal/models/core/class_object_meta.pyi +0 -15
- amsdal/models/core/class_property_meta.py +0 -15
- amsdal/models/core/class_property_meta.pyi +0 -10
- amsdal/schemas/core/class_object_meta/model.json +0 -59
- amsdal/schemas/core/class_property_meta/model.json +0 -23
- {amsdal-0.4.12.dist-info → amsdal-0.5.0.dist-info}/licenses/LICENSE.txt +0 -0
- {amsdal-0.4.12.dist-info → amsdal-0.5.0.dist-info}/top_level.txt +0 -0
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
@@ -9,21 +9,22 @@ from pydantic.fields import Field
|
|
9
9
|
from pydantic.functional_validators import field_validator
|
10
10
|
|
11
11
|
from amsdal.models.core.class_property import * # noqa: F403
|
12
|
+
from amsdal.models.core.storage_metadata import * # noqa: F403
|
12
13
|
|
13
14
|
|
14
15
|
class ClassObject(Model):
|
15
16
|
__module_type__: ClassVar[ModuleType] = ModuleType.CORE
|
17
|
+
title: str = Field(title='Title')
|
18
|
+
type: str = Field(title='Type')
|
19
|
+
module_type: str = Field(title='Module Type')
|
16
20
|
properties: Optional[dict[str, Optional['ClassProperty']]] = Field(None, title='Properties') # noqa: F405, UP007
|
17
|
-
table_name: Optional[str] = Field(None, title='Table name') # noqa: UP007
|
18
|
-
primary_key: Optional[list[str]] = Field(None, title='Primary key fields') # noqa: UP007
|
19
|
-
indexed: Optional[list[str]] = Field(None, title='Indexed') # noqa: UP007
|
20
|
-
unique: Optional[list[list[str]]] = Field(None, title='Unique Fields') # noqa: UP007
|
21
21
|
required: Optional[list[str]] = Field(None, title='Required') # noqa: UP007
|
22
|
-
|
22
|
+
custom_code: str | None = Field(None, title='Custom Code')
|
23
|
+
storage_metadata: Optional['StorageMetadata'] = Field(None, title='Storage metadata') # noqa: F405
|
23
24
|
|
24
25
|
@field_validator('properties')
|
25
26
|
@classmethod
|
26
|
-
def _non_empty_keys_properties(cls: type, value: Any) -> Any:
|
27
|
+
def _non_empty_keys_properties(cls: type, value: Any) -> Any: # type: ignore # noqa: A003
|
27
28
|
return validate_non_empty_keys(value)
|
28
29
|
|
29
30
|
@property
|
@@ -1,17 +1,18 @@
|
|
1
1
|
from amsdal.models.core.class_property import *
|
2
|
+
from amsdal.models.core.storage_metadata import *
|
2
3
|
from amsdal_models.classes.model import Model
|
3
4
|
from amsdal_utils.models.enums import ModuleType
|
4
5
|
from typing import Any, ClassVar
|
5
6
|
|
6
7
|
class ClassObject(Model):
|
7
8
|
__module_type__: ClassVar[ModuleType] = ...
|
9
|
+
title: str = ...
|
10
|
+
type: str = ...
|
11
|
+
module_type: str = ...
|
8
12
|
properties: dict[str, ClassProperty | None] | None = ...
|
9
|
-
table_name: str | None = ...
|
10
|
-
primary_key: list[str] | None = ...
|
11
|
-
indexed: list[str] | None = ...
|
12
|
-
unique: list[list[str]] | None = ...
|
13
13
|
required: list[str] | None = ...
|
14
|
-
|
14
|
+
custom_code: str | None = ...
|
15
|
+
storage_metadata: StorageMetadata | None = ...
|
15
16
|
@classmethod
|
16
17
|
def _non_empty_keys_properties(cls, value: Any) -> Any: ...
|
17
18
|
@property
|
@@ -7,12 +7,17 @@ from amsdal_utils.models.enums import ModuleType
|
|
7
7
|
from pydantic.fields import Field
|
8
8
|
from pydantic.functional_validators import field_validator
|
9
9
|
|
10
|
+
from amsdal.models.core.option import * # noqa: F403
|
11
|
+
|
10
12
|
|
11
13
|
class ClassProperty(TypeModel):
|
12
14
|
__module_type__: ClassVar[ModuleType] = ModuleType.CORE
|
15
|
+
title: str | None = Field(None, title='Title')
|
13
16
|
type: str = Field(title='Type')
|
17
|
+
default: Any | None = Field(None, title='Default')
|
18
|
+
options: list['Option'] | None = Field(None, title='Options') # noqa: F405
|
14
19
|
items: dict[str, Any | None] | None = Field(None, title='Items')
|
15
|
-
|
20
|
+
discriminator: str | None = Field(None, title='Discriminator')
|
16
21
|
|
17
22
|
@field_validator('items')
|
18
23
|
@classmethod
|
@@ -1,11 +1,15 @@
|
|
1
|
+
from amsdal.models.core.option import *
|
1
2
|
from amsdal_models.classes.model import TypeModel
|
2
3
|
from amsdal_utils.models.enums import ModuleType
|
3
4
|
from typing import Any, ClassVar
|
4
5
|
|
5
6
|
class ClassProperty(TypeModel):
|
6
7
|
__module_type__: ClassVar[ModuleType]
|
8
|
+
title: str | None
|
7
9
|
type: str
|
10
|
+
default: Any | None
|
11
|
+
options: list['Option'] | None
|
8
12
|
items: dict[str, Any | None] | None
|
9
|
-
|
13
|
+
discriminator: str | None
|
10
14
|
@classmethod
|
11
15
|
def _non_empty_keys_items(cls, value: Any) -> Any: ...
|
@@ -0,0 +1,15 @@
|
|
1
|
+
from typing import ClassVar
|
2
|
+
from typing import Optional
|
3
|
+
|
4
|
+
from amsdal_models.classes.model import TypeModel
|
5
|
+
from amsdal_utils.models.enums import ModuleType
|
6
|
+
from pydantic.fields import Field
|
7
|
+
|
8
|
+
|
9
|
+
class StorageMetadata(TypeModel):
|
10
|
+
__module_type__: ClassVar[ModuleType] = ModuleType.CORE
|
11
|
+
table_name: Optional[str] = Field(None, title='Table name') # noqa: UP007
|
12
|
+
db_fields: dict[str, list[str]] | None = Field(None, title='Database fields')
|
13
|
+
primary_key: Optional[list[str]] = Field(None, title='Primary key fields') # noqa: UP007
|
14
|
+
indexed: Optional[list[list[str]]] = Field(None, title='Indexed') # noqa: UP007
|
15
|
+
unique: Optional[list[list[str]]] = Field(None, title='Unique Fields') # noqa: UP007
|
@@ -0,0 +1,11 @@
|
|
1
|
+
from amsdal_models.classes.model import TypeModel
|
2
|
+
from amsdal_utils.models.enums import ModuleType
|
3
|
+
from typing import ClassVar
|
4
|
+
|
5
|
+
class StorageMetadata(TypeModel):
|
6
|
+
__module_type__: ClassVar[ModuleType]
|
7
|
+
table_name: str | None
|
8
|
+
db_fields: dict[str, list[str]] | None
|
9
|
+
primary_key: list[str] | None
|
10
|
+
indexed: list[list[str]] | None
|
11
|
+
unique: list[list[str]] | None
|
amsdal/models/types/object.py
CHANGED
@@ -11,12 +11,12 @@ from pydantic.functional_validators import field_validator
|
|
11
11
|
|
12
12
|
class Object(Model):
|
13
13
|
__module_type__: ClassVar[ModuleType] = ModuleType.TYPE
|
14
|
-
title:
|
15
|
-
type:
|
14
|
+
title: str = Field(title='Title')
|
15
|
+
type: str = Field(title='Type')
|
16
|
+
module_type: str = Field(title='Module Type')
|
16
17
|
default: Optional[Any] = Field(None, title='Default') # noqa: UP007
|
17
18
|
properties: Optional[dict[str, Optional[Any]]] = Field(None, title='Properties') # noqa: UP007
|
18
19
|
required: Optional[list[str]] = Field(None, title='Required') # noqa: UP007
|
19
|
-
unique: Optional[list[list[str]]] = Field(None, title='Unique Fields') # noqa: UP007
|
20
20
|
custom_code: Optional[str] = Field(None, title='Custom Code') # noqa: UP007
|
21
21
|
meta_class: Optional[str] = Field(None, title='Meta Class') # noqa: UP007
|
22
22
|
|
amsdal/models/types/object.pyi
CHANGED
@@ -4,12 +4,12 @@ from typing import Any, ClassVar
|
|
4
4
|
|
5
5
|
class Object(Model):
|
6
6
|
__module_type__: ClassVar[ModuleType] = ...
|
7
|
-
title: str
|
8
|
-
type: str
|
7
|
+
title: str = ...
|
8
|
+
type: str = ...
|
9
|
+
module_type: str = ...
|
9
10
|
default: Any | None = ...
|
10
11
|
properties: dict[str, Any | None] | None = ...
|
11
12
|
required: list[str] | None = ...
|
12
|
-
unique: list[list[str]] | None = ...
|
13
13
|
custom_code: str | None = ...
|
14
14
|
meta_class: str | None = ...
|
15
15
|
@classmethod
|
@@ -2,6 +2,18 @@
|
|
2
2
|
"title": "ClassObject",
|
3
3
|
"type": "object",
|
4
4
|
"properties": {
|
5
|
+
"title": {
|
6
|
+
"title": "Title",
|
7
|
+
"type": "string"
|
8
|
+
},
|
9
|
+
"type": {
|
10
|
+
"title": "Type",
|
11
|
+
"type": "string"
|
12
|
+
},
|
13
|
+
"module_type": {
|
14
|
+
"title": "Module Type",
|
15
|
+
"type": "string"
|
16
|
+
},
|
5
17
|
"properties": {
|
6
18
|
"title": "Properties",
|
7
19
|
"type": "dictionary",
|
@@ -21,6 +33,14 @@
|
|
21
33
|
"type": "string"
|
22
34
|
}
|
23
35
|
},
|
36
|
+
"custom_code": {
|
37
|
+
"title": "Custom Code",
|
38
|
+
"type": "string"
|
39
|
+
},
|
40
|
+
"storage_metadata": {
|
41
|
+
"title": "Storage metadata",
|
42
|
+
"type": "StorageMetadata"
|
43
|
+
},
|
24
44
|
"meta_class": {
|
25
45
|
"title": "Meta Class",
|
26
46
|
"type": "string",
|
@@ -2,10 +2,25 @@
|
|
2
2
|
"title": "ClassProperty",
|
3
3
|
"type": "object",
|
4
4
|
"properties": {
|
5
|
+
"title": {
|
6
|
+
"title": "Title",
|
7
|
+
"type": "string"
|
8
|
+
},
|
5
9
|
"type": {
|
6
10
|
"title": "Type",
|
7
11
|
"type": "string"
|
8
12
|
},
|
13
|
+
"default": {
|
14
|
+
"title": "Default",
|
15
|
+
"type": "anything"
|
16
|
+
},
|
17
|
+
"options": {
|
18
|
+
"title": "Options",
|
19
|
+
"type": "array",
|
20
|
+
"items": {
|
21
|
+
"type": "Option"
|
22
|
+
}
|
23
|
+
},
|
9
24
|
"items": {
|
10
25
|
"title": "Items",
|
11
26
|
"type": "dictionary",
|
@@ -13,6 +28,10 @@
|
|
13
28
|
"key": {"type": "string"},
|
14
29
|
"value": {"type": "anything"}
|
15
30
|
}
|
31
|
+
},
|
32
|
+
"discriminator": {
|
33
|
+
"title": "Discriminator",
|
34
|
+
"type": "string"
|
16
35
|
}
|
17
36
|
},
|
18
37
|
"required": [
|
@@ -0,0 +1,52 @@
|
|
1
|
+
{
|
2
|
+
"title": "StorageMetadata",
|
3
|
+
"type": "object",
|
4
|
+
"properties": {
|
5
|
+
"table_name": {
|
6
|
+
"title": "Table name",
|
7
|
+
"type": "string"
|
8
|
+
},
|
9
|
+
"db_fields": {
|
10
|
+
"title": "Database fields",
|
11
|
+
"type": "dictionary",
|
12
|
+
"items": {
|
13
|
+
"key": {"type": "string"},
|
14
|
+
"value": {
|
15
|
+
"type": "array",
|
16
|
+
"items": {
|
17
|
+
"type": "string"
|
18
|
+
}
|
19
|
+
}
|
20
|
+
}
|
21
|
+
},
|
22
|
+
"primary_key": {
|
23
|
+
"title": "Primary key fields",
|
24
|
+
"type": "array",
|
25
|
+
"items": {
|
26
|
+
"type": "string"
|
27
|
+
}
|
28
|
+
},
|
29
|
+
"indexed": {
|
30
|
+
"title": "Indexed",
|
31
|
+
"type": "array",
|
32
|
+
"items": {
|
33
|
+
"type": "array",
|
34
|
+
"items": {
|
35
|
+
"type": "string"
|
36
|
+
}
|
37
|
+
}
|
38
|
+
},
|
39
|
+
"unique": {
|
40
|
+
"title": "Unique Fields",
|
41
|
+
"type": "array",
|
42
|
+
"items": {
|
43
|
+
"type": "array",
|
44
|
+
"items": {
|
45
|
+
"type": "string"
|
46
|
+
}
|
47
|
+
}
|
48
|
+
}
|
49
|
+
},
|
50
|
+
"required": [],
|
51
|
+
"meta_class": "TypeMeta"
|
52
|
+
}
|
Binary file
|
Binary file
|
Binary file
|
amsdal/utils/tests/helpers.py
CHANGED
@@ -20,8 +20,8 @@ from amsdal_data.connections.historical.schema_version_manager import Historical
|
|
20
20
|
from amsdal_models.migration import migrations
|
21
21
|
from amsdal_models.migration.executors.default_executor import DefaultAsyncMigrationExecutor
|
22
22
|
from amsdal_models.migration.executors.default_executor import DefaultMigrationExecutor
|
23
|
-
from amsdal_models.migration.file_migration_executor import
|
24
|
-
from amsdal_models.migration.file_migration_generator import
|
23
|
+
from amsdal_models.migration.file_migration_executor import SimpleFileMigrationExecutorManager
|
24
|
+
from amsdal_models.migration.file_migration_generator import SimpleFileMigrationGenerator
|
25
25
|
from amsdal_models.migration.file_migration_writer import FileMigrationWriter
|
26
26
|
from amsdal_models.migration.migrations import MigrateData
|
27
27
|
from amsdal_models.migration.migrations import MigrationSchemas
|
@@ -333,7 +333,7 @@ def migrate() -> None:
|
|
333
333
|
_schemas_map = {_schema.title: _schema for _schema in _schemas}
|
334
334
|
|
335
335
|
for object_schema in _schemas:
|
336
|
-
for _operation_data in
|
336
|
+
for _operation_data in SimpleFileMigrationGenerator.build_operations(
|
337
337
|
ModuleType.USER,
|
338
338
|
object_schema,
|
339
339
|
None,
|
@@ -348,7 +348,7 @@ def migrate() -> None:
|
|
348
348
|
_operation.forward(executor)
|
349
349
|
|
350
350
|
for object_schema in _cycle_schemas:
|
351
|
-
for _operation_data in
|
351
|
+
for _operation_data in SimpleFileMigrationGenerator.build_operations(
|
352
352
|
ModuleType.USER,
|
353
353
|
object_schema,
|
354
354
|
_schemas_map[object_schema.title],
|
@@ -367,7 +367,7 @@ def migrate() -> None:
|
|
367
367
|
|
368
368
|
def _migrate_per_loader(executor: DefaultMigrationExecutor, loader: MigrationsLoader) -> None:
|
369
369
|
for _migration in loader:
|
370
|
-
migration_class =
|
370
|
+
migration_class = SimpleFileMigrationExecutorManager.get_migration_class(_migration)
|
371
371
|
migration_class_instance = migration_class()
|
372
372
|
|
373
373
|
for _operation in migration_class_instance.operations:
|
@@ -413,7 +413,7 @@ async def async_migrate() -> None:
|
|
413
413
|
_schemas_map = {_schema.title: _schema for _schema in _schemas}
|
414
414
|
|
415
415
|
for object_schema in _schemas:
|
416
|
-
for _operation_data in
|
416
|
+
for _operation_data in SimpleFileMigrationGenerator.build_operations(
|
417
417
|
ModuleType.USER,
|
418
418
|
object_schema,
|
419
419
|
None,
|
@@ -428,7 +428,7 @@ async def async_migrate() -> None:
|
|
428
428
|
_operation.forward(executor)
|
429
429
|
|
430
430
|
for object_schema in _cycle_schemas:
|
431
|
-
for _operation_data in
|
431
|
+
for _operation_data in SimpleFileMigrationGenerator.build_operations(
|
432
432
|
ModuleType.USER,
|
433
433
|
object_schema,
|
434
434
|
_schemas_map[object_schema.title],
|
@@ -447,7 +447,7 @@ async def async_migrate() -> None:
|
|
447
447
|
|
448
448
|
async def _async_migrate_per_loader(executor: DefaultAsyncMigrationExecutor, loader: MigrationsLoader) -> None:
|
449
449
|
for _migration in loader:
|
450
|
-
migration_class =
|
450
|
+
migration_class = SimpleFileMigrationExecutorManager.get_migration_class(_migration)
|
451
451
|
migration_class_instance = migration_class()
|
452
452
|
|
453
453
|
for _operation in migration_class_instance.operations:
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: amsdal
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.5.0
|
4
4
|
Summary: AMSDAL
|
5
5
|
License: AMSDAL End User License Agreement
|
6
6
|
|
@@ -129,9 +129,9 @@ Requires-Dist: cryptography~=42.0
|
|
129
129
|
Requires-Dist: httpx~=0.25
|
130
130
|
Requires-Dist: bcrypt~=4.0
|
131
131
|
Requires-Dist: black>=24.3.0
|
132
|
-
Requires-Dist: amsdal_utils==0.
|
133
|
-
Requires-Dist: amsdal_data[postgres-binary]==0.
|
134
|
-
Requires-Dist: amsdal_models
|
132
|
+
Requires-Dist: amsdal_utils==0.5.*
|
133
|
+
Requires-Dist: amsdal_data[postgres-binary]==0.5.*
|
134
|
+
Requires-Dist: amsdal_models==0.5.*
|
135
135
|
Requires-Dist: pip>=21.3.1
|
136
136
|
Provides-Extra: cli
|
137
137
|
Requires-Dist: amsdal-cli==0.4.*; extra == "cli"
|