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

Expands upon project step documentation #9151

Merged
merged 4 commits into from
Apr 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions docs/api-ref/prefect/projects/base.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
description: Prefect Python API for projects.
tags:
- Python API
- projects
- deployments
- steps
---

::: prefect.projects.base
10 changes: 10 additions & 0 deletions docs/api-ref/prefect/projects/steps/core.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
description: Prefect Python API for project steps.
tags:
- Python API
- projects
- deployments
- steps
---

::: prefect.projects.steps.core
11 changes: 11 additions & 0 deletions docs/api-ref/prefect/projects/steps/pull.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
description: Prefect Python API for pull steps for projects.
tags:
- Python API
- projects
- deployments
- steps
- pull step
---

::: prefect.projects.steps.pull
10 changes: 8 additions & 2 deletions docs/concepts/projects.md
Original file line number Diff line number Diff line change
Expand Up @@ -222,12 +222,18 @@ Registration also allows users to share their projects without requiring a full

## Deployment mechanics

Anytime you run `prefect deploy`, the following steps are taken in order:
Anytime you run `prefect deploy`, the following actions are taken in order:

- the project `prefect.yaml` file is loaded; first, the `prefect.yaml` `build` section is loaded and all variable and block references are resolved. The steps are then run in the order provided
- next, the `push` section is loaded and all variable and block references are resolved; the steps within this section are then run in the order provided
- lastly, the `pull` section is templated with any step outputs but *is not run*. Note that block references are _not_ hydrated for security purposes - block references are always resolved at runtime
- Next, the project `deployment.yaml` file is loaded. All variable and block references are resolved. All flags provided via the `prefect deploy` CLI are then overlaid on the values loaded from the file.
- The final step occurs when the fully realized deployment specification is registered with the Prefect API

Note that anytime an `ImportError` is encountered while executing a step, the `requires` field is looked up and installed. The step then retries with the newly installed dependency.
Anytime a step is run, the following actions are taken in order:

- The step's inputs and block / variable references are resolved (see [the templating documentation above](#templating-options) for more details)
- The step's function is imported; if it cannot be found, the special `requires` keyword is used to install the necessary packages
- The step's function is called with the resolved inputs
- The step's output is returned and used to resolve inputs for subsequent steps

4 changes: 4 additions & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,10 @@ nav:
- 'prefect.futures': api-ref/prefect/futures.md
- 'prefect.infrastructure': api-ref/prefect/infrastructure.md
- 'prefect.logging': api-ref/prefect/logging.md
- 'prefect.projects':
- 'prefect.projects.base': api-ref/prefect/projects/base.md
- 'prefect.projects.steps.core': api-ref/prefect/projects/steps/core.md
- 'prefect.projects.steps.pull': api-ref/prefect/projects/steps/pull.md
- 'prefect.runtime':
- 'prefect.runtime.flow_run': api-ref/prefect/runtime/flow_run.md
- 'prefect.runtime.deployment': api-ref/prefect/runtime/deployment.md
Expand Down
53 changes: 41 additions & 12 deletions src/prefect/projects/base.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
"""
Core primitives for managing Prefect projects.
Core primitives for managing Prefect projects. Projects provide a minimally opinionated
build system for managing flows and deployments.

To get started, follow along with [the project tutorial](/tutorials/projects/).
"""
import json
import os
Expand All @@ -18,9 +21,12 @@

def find_prefect_directory(path: Path = None) -> Optional[Path]:
"""
Recurses upward looking for .prefect/ directories. If found is never found, `None` is returned.
Given a path, recurses upward looking for .prefect/ directories.

Once found, returns absolute path to the ./prefect directory, which is assumed to reside within the
root for the current project.

Returns the absolute location of the .prefect/ directory.
If one is never found, `None` is returned.
"""
path = Path(path or ".").resolve()
parent = path.parent.resolve()
Expand All @@ -35,7 +41,7 @@ def find_prefect_directory(path: Path = None) -> Optional[Path]:

def create_default_deployment_yaml(path: str) -> bool:
"""
Creates default deployment.yaml file in the provided path if one does not already exist;
Creates default `deployment.yaml` file in the provided path if one does not already exist;
returns boolean specifying whether a file was created.
"""
path = Path(path)
Expand All @@ -47,12 +53,14 @@ def create_default_deployment_yaml(path: str) -> bool:
return True


def set_prefect_hidden_dir() -> bool:
def set_prefect_hidden_dir(path: str = None) -> bool:
"""
Creates default .prefect directory if one does not already exist.
Returns boolean specifying whether a directory was created.
Creates default `.prefect/` directory if one does not already exist.
Returns boolean specifying whether or not a directory was created.

If a path is provided, the directory will be created in that location.
"""
path = Path(".") / ".prefect"
path = Path(path or ".") / ".prefect"

# use exists so that we dont accidentally overwrite a file
if path.exists():
Expand All @@ -65,8 +73,14 @@ def create_default_project_yaml(
path: str, name: str = None, contents: dict = None
) -> bool:
"""
Creates default prefect.yaml file in the provided path if one does not already exist;
Creates default `prefect.yaml` file in the provided path if one does not already exist;
returns boolean specifying whether a file was created.

Args:
name (str, optional): the name of the project; if not provided, the current directory name
will be used
contents (dict, optional): a dictionary of contents to write to the file; if not provided,
defaults will be used
"""
path = Path(path)
if (path / "prefect.yaml").exists():
Expand Down Expand Up @@ -119,7 +133,12 @@ def configure_project_by_recipe(recipe: str, **formatting_kwargs) -> dict:
"""
Given a recipe name, returns a dictionary representing base configuration options.

Raises ValueError if recipe does not exist.
Args:
recipe (str): the name of the recipe to use
formatting_kwargs (dict, optional): additional keyword arguments to format the recipe

Raises:
ValueError: if provided recipe name does not exist.
"""
# load the recipe
recipe_path = Path(__file__).parent / "recipes" / recipe / "prefect.yaml"
Expand All @@ -142,7 +161,12 @@ def initialize_project(name: str = None, recipe: str = None) -> List[str]:
Initializes a basic project structure with base files. If no name is provided, the name
of the current directory is used. If no recipe is provided, one is inferred.

Returns a list of files / directories that were created.
Args:
name (str, optional): the name of the project; if not provided, the current directory name
recipe (str, optional): the name of the recipe to use; if not provided, one is inferred

Returns:
List[str]: a list of files / directories that were created
"""
# determine if in git repo or use directory name as a default
is_git_based = False
Expand Down Expand Up @@ -211,7 +235,12 @@ async def register_flow(entrypoint: str, force: bool = False):
"""
Register a flow with this project from an entrypoint.

Raises a ValueError if registration will overwrite an existing known flow.
Args:
entrypoint (str): the entrypoint to the flow to register
force (bool, optional): whether or not to overwrite an existing flow with the same name

Raises:
ValueError: if `force` is `False` and registration would overwrite an existing flow
"""
try:
fpath, obj_name = entrypoint.rsplit(":", 1)
Expand Down
20 changes: 14 additions & 6 deletions src/prefect/projects/steps/core.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
"""
Core primitives for managing Prefect projects.
Core primitives for running Prefect project steps.

Project steps are YAML representations of Python functions along with their inputs.

Whenever a step is run, the following actions are taken:

- The step's inputs and block / variable references are resolved (see [the projects concepts documentation](/concepts/projects/#templating-options) for more details)
- The step's function is imported; if it cannot be found, the `requires` keyword is used to install the necessary packages
- The step's function is called with the resolved inputs
- The step's output is returned and used to resolve inputs for subsequent steps
"""
import subprocess
import sys
Expand Down Expand Up @@ -41,13 +50,12 @@ async def run_step(step: dict) -> dict:
"""
Runs a step, returns the step's output.

Steps are assumed to be in the format {"importable.func.name": {"kwarg1": "value1", ...}}
Steps are assumed to be in the format `{"importable.func.name": {"kwarg1": "value1", ...}}`.

The following keywords are reserved for specific purposes and will be removed from the
The 'requires' keyword is reserved for specific purposes and will be removed from the
inputs before passing to the step function:
requires: A package or list of packages needed to run the step function. If the step
function cannot be imported, the packages will be installed and the step function
will be re-imported.

This keyword is used to specify packages that should be installed before running the step.
"""
fqn, inputs = step.popitem()

Expand Down
24 changes: 23 additions & 1 deletion src/prefect/projects/steps/pull.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,16 @@


def set_working_directory(directory: str) -> dict:
"""
Sets the working directory; works with both absolute and relative paths.

Args:
directory (str): the directory to set as the working directory

Returns:
dict: a dictionary containing a `directory` key of the
directory that was set
"""
os.chdir(directory)
return dict(directory=directory)

Expand All @@ -17,7 +27,19 @@ def git_clone_project(
repository: str, branch: Optional[str] = None, access_token: Optional[str] = None
) -> dict:
"""
Just a repo name will be assumed GitHub, otherwise provide a full repo_url.
Clones a git repository into the current working directory.

Args:
repository (str): the URL of the repository to clone
branch (str, optional): the branch to clone; if not provided, the default branch will be used
access_token (str, optional): an access token to use for cloning the repository; if not provided
the repository will be cloned using the default git credentials

Returns:
dict: a dictionary containing a `directory` key of the new directory that was created

Raises:
subprocess.CalledProcessError: if the git clone command fails for any reason
"""
url_components = urllib.parse.urlparse(repository)
if url_components.scheme == "https" and access_token is not None:
Expand Down