Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Project definition v2 entity schemas: application and application package #1280

Merged
merged 33 commits into from
Jul 12, 2024

Conversation

sfc-gh-gbloom
Copy link
Contributor

@sfc-gh-gbloom sfc-gh-gbloom commented Jul 3, 2024

Pre-review checklist

  • I've confirmed that instructions included in README.md are still correct after my changes in the codebase.
  • I've added or updated automated unit tests to verify correctness of my new code.
  • I've added or updated integration tests to verify correctness of my new code.
  • I've confirmed that my changes are working by executing CLI's commands manually on MacOS.
  • I've confirmed that my changes are working by executing CLI's commands manually on Windows.
  • I've confirmed that my changes are up-to-date with the target branch.
  • I've described my changes in the release notes.
  • I've described my changes in the section below.

Changes description

The changes in this PR are a step towards enabling definition_version: 2 in snowflake.yml. The changes are behind the hidden snow ws validate command, which requires enabling the enable_project_definition_v2 feature flag.

Changed the v2 schema to have a proper entities field that validates the type of each entity and instantiates the matching Pydantic entity model:

definition_version: 2
entities:
  pkg:
    type: application package
    ...

And added schemas for the ApplicationPackageEntity and ApplicationEntity entities.

Infra features:

  • Added the EntityBase class for all entities to extend
  • All entities share the same MetaField
  • Values specified in DefaultsField will be added if the field exists on the schema class and not specified by the user
  • TargetField takes a generic entity type, which is verified as part of the schema validation

Test

Two manual tests that were not simple to automate:

  1. The generic type passed to TargetField is enforced. Changed ApplicationEntity.from.target to match a different type:
class ApplicationFromField(UpdatableModel):
-    target: TargetField[ApplicationPackageEntity] = Field(
+    target: TargetField[ApplicationEntity] = Field(
        title="Reference to an application package entity",
    )

And verified that snow ws validate fails:

Target type mismatch. Expected ApplicationEntity, got ApplicationPackageEntity
  1. Tested that template vars are correctly expanded:
definition_version: 2
entities:
  pkg:
    type: application package
    name: my_app_pkg_<% ctx.env.foo %>
    artifacts: []
    manifest: src/manifest.yml
env:
  foo: bar

Added print(cli_context.project_definition) to the validate command and verified the value of name is my_app_pkg_bar:

$ snow ws validate

definition_version='2' entities={'pkg': ApplicationPackageEntity(type_=<EntityType.APPLICATION_PACKAGE: 'application package'>, meta=None, name='my_app_pkg_bar', artifacts=[], deploy_root='output/deploy/', stage='app_src.stage', distribution='internal', manifest='src/manifest.yml')} defaults=None env=ProjectEnvironment(override_env={}, default_env={'foo': 'bar'})

Project definition is valid.

Base automatically changed from pdfv2-schema to main July 8, 2024 14:53
@sfc-gh-gbloom sfc-gh-gbloom force-pushed the pdfv2-app-package-entity branch from 7cafab1 to 0eaf420 Compare July 8, 2024 15:34
@sfc-gh-gbloom sfc-gh-gbloom force-pushed the pdfv2-app-package-entity branch from 7f38deb to 244483a Compare July 8, 2024 17:23
@sfc-gh-gbloom sfc-gh-gbloom marked this pull request as ready for review July 8, 2024 17:52
@sfc-gh-gbloom sfc-gh-gbloom requested review from a team as code owners July 8, 2024 17:52
@sfc-gh-gbloom sfc-gh-gbloom changed the title Workspaces entities implementation: application package and application Project definition v2 entities implementation: application package and application Jul 8, 2024
@sfc-gh-gbloom sfc-gh-gbloom changed the title Project definition v2 entities implementation: application package and application Project definition v2 entity schemas: application and application package Jul 8, 2024
)


class ApplicationFromField(UpdatableModel):
Copy link
Contributor

@sfc-gh-cgorrie sfc-gh-cgorrie Jul 8, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Non-blocking) In the future, we should pull the generic type attr up, e.g.

from_: EntityReferenceField[ApplicationPackageEntity]

)


class EntityBase(ABC, UpdatableModel):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Non-blocking) I would like to see e.g. StageBasedEntity, DatabaseLevelEntity, SchemaLevelEntity, AccountLevelEntity in the future to maximize re-use and align with the func spec

Comment on lines 45 to 48
_v2_entity_types_map = {
EntityType.APPLICATION.value: ApplicationEntity,
EntityType.APPLICATION_PACKAGE.value: ApplicationPackageEntity,
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Next up for us is to figure out how these pydantic models map to classes with e.g. deploy, drop, bundle methods

from snowflake.cli.api.project.schemas.native_app.path_mapping import PathMapping


class ApplicationPackageEntity(EntityBase):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are some properties not accounted for here, like scratch_stage, generated_root, and bundle_root. What's the plan for those?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added these as well. While we maintain both versions we will have to remember to update v2 schema when we make v1 changes.

sfc-gh-cgorrie
sfc-gh-cgorrie previously approved these changes Jul 9, 2024
title="Distribution of the application package created by the Snowflake CLI",
default="internal",
)
manifest: str = Field(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
manifest: str = Field(
manifest: Path = Field(

Or SecurePath.

Comment on lines 32 to 34
class EntityType(Enum):
APPLICATION = "application"
APPLICATION_PACKAGE = "application package"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have those strings as literal on entities classes and we have them here. Should we consider using this Enum as source of truth? Or should we use entities to build Enum / something?

Taking into account that we are constructing Union[ApplicationEntity, ApplicationPackageEntity] and

_v2_entity_types_map = {
    EntityType.APPLICATION.value: ApplicationEntity,
    EntityType.APPLICATION_PACKAGE.value: ApplicationPackageEntity,
}

It means that adding new entity requires it to be registered in at least two places.

it may be wise to have

KNOWN_ENTITIES = [
  ApplicationEntity,
  ApplicationPackageEntity
]

ENTITIES_TYPE = Union[*KNOWN_ENTITIES]
_v2_entity_types_map = {get_args(eval(inspect.get_annotations(e)["type"]))[0]: e for e in KNOWN_ENTITIES}

The dict comprehension is a bit of a magic, but probably there's something smarter we can do.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that's what I tried at first but I couldn't make pydantic understand enums as discriminators and reverted to literal strings.

I'm not sure what I think about this dict comprehension, but I get the idea. I'll try to come up with a cleaner way to have a single source of truth.

title="Role to use when creating the entity object",
default=None,
)
post_deploy: Optional[List[ApplicationPostDeployHook]] = Field(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this common for all entities?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep

Comment on lines 138 to 139
if "defaults" in data:
if "entities" in data:
Copy link
Contributor

@sfc-gh-astus sfc-gh-astus Jul 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if "defaults" in data and "entities" in data:

@sfc-gh-gbloom sfc-gh-gbloom enabled auto-merge (squash) July 11, 2024 02:46

ALL_ENTITIES = [*get_args(Entity)]

v2_entity_types_map = {e.get_type(): e for e in ALL_ENTITIES}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!

Comment on lines 148 to 161
# TODO Automatically detect TargetFields to validate
if entity.type == ApplicationEntity.get_type():
if isinstance(entity.from_.target, TargetField):
target = str(entity.from_.target)
if target not in entities:
raise ValueError(f"No such target: {target}")
else:
# Validate the target type
target_cls = entity.from_.__class__.model_fields["target"]
target_type = target_cls.annotation.__args__[0]
actual_target_type = entities[target].__class__
if target_type and target_type is not actual_target_type:
raise ValueError(
f"Target type mismatch. Expected {target_type.__name__}, got {actual_target_type.__name__}"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I imagine that there will more checks on the enitites should we consider something like

Suggested change
# TODO Automatically detect TargetFields to validate
if entity.type == ApplicationEntity.get_type():
if isinstance(entity.from_.target, TargetField):
target = str(entity.from_.target)
if target not in entities:
raise ValueError(f"No such target: {target}")
else:
# Validate the target type
target_cls = entity.from_.__class__.model_fields["target"]
target_type = target_cls.annotation.__args__[0]
actual_target_type = entities[target].__class__
if target_type and target_type is not actual_target_type:
raise ValueError(
f"Target type mismatch. Expected {target_type.__name__}, got {actual_target_type.__name__}"
# TODO Automatically detect TargetFields to validate
if entity.type == ApplicationEntity.get_type():
self._validate_application_targets(entity)

WDYT?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I split it into 2 methods to have slightly better separation, but we will need to refactor deeper when we find a generic way for this validation. Will address it in a dedicated PR.

)
generated_root: Optional[Path] = Field(
title="Subdirectory of the deploy root where files generated by the Snowflake CLI will be written.",
default="__generated/",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should default be a Path or conversion happens automagicaly?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automagically, but made it Path here too to make it clearer

@sfc-gh-gbloom sfc-gh-gbloom merged commit 426173a into main Jul 12, 2024
16 checks passed
@sfc-gh-gbloom sfc-gh-gbloom deleted the pdfv2-app-package-entity branch July 12, 2024 13:00
sfc-gh-sichen pushed a commit that referenced this pull request Oct 17, 2024
…kage (#1280)

* application and application package entity schemas

* add integration test

* simplify entity type, use discriminator

* create types map dynamically

* get_type method

* read entity list from union
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants