

# AWS Glue 中的开发蓝图
<a name="developing-blueprints"></a>

作为 AWS Glue 开发人员，您可以创建和发布蓝图，以便数据分析人员可以使用这些蓝图来生成工作流。

**Topics**
+ [开发蓝图概述](developing-blueprints-overview.md)
+ [开发蓝图的先决条件](developing-blueprints-prereq.md)
+ [编写蓝图代码](developing-blueprints-code.md)
+ [示例蓝图项目](developing-blueprints-sample.md)
+ [测试蓝图](developing-blueprints-testing.md)
+ [发布蓝图](developing-blueprints-publishing.md)
+ [AWS Glue 蓝图类参考](developing-blueprints-code-classes.md)
+ [蓝图示例](developing-blueprints-samples.md)

**另请参阅**  
[AWS Glue 中的蓝图概览](blueprints-overview.md)

# 开发蓝图概述
<a name="developing-blueprints-overview"></a>

开发过程的第一步是确定将受益于蓝图的常见使用案例。典型使用案例涉及一个反复出现的 ETL 问题，您认为此问题应该以常规方式解决。接下来，设计一个实现通用使用案例的蓝图，并定义蓝图输入参数，这些参数都可从通用使用案例中定义特定使用案例。

蓝图由包含蓝图参数配置文件的项目以及定义所要生成工作流*布局*的脚本组成。布局定义了所要创建的任务和爬网程序（或蓝图脚本术语中的*实体*）。

您不会直接在布局脚本中指定任何触发器。相反，您可以编写代码来指定脚本创建的任务和爬网程序之间的依赖关系。AWS Glue 根据您的依赖项规范生成触发器。布局脚本的输出是一个工作流对象，其中包含所有工作流实体的规范。

您可以使用以下 AWS Glue 蓝图库构建工作流对象：
+ `awsglue.blueprint.base_resource` – 库使用的基本资源库。
+ `awsglue.blueprint.workflow` – 用于定义 `Workflow` 类的库。
+ `awsglue.blueprint.job` – 用于定义 `Job` 类的库。
+ `awsglue.blueprint.crawler` – 用于定义 `Crawler` 类的库。

唯一支持布局生成的其他库是可用于 Python shell 的库。

在发布蓝图之前，您可以使用蓝图库中定义的方法在本地测试蓝图。

当您准备好将蓝图提供给数据分析人员时，您可以将脚本、参数配置文件和任何支持文件（如附加脚本和库）打包到单个可部署资产中。然后，您将资产上传到 Amazon S3，并要求管理员将其注册到 AWS Glue。

有关更多示例蓝图项目的信息，请参阅[示例蓝图项目](developing-blueprints-sample.md)和[蓝图示例](developing-blueprints-samples.md)。

# 开发蓝图的先决条件
<a name="developing-blueprints-prereq"></a>

要开发蓝图，您应熟悉使用 AWS Glue 和为 Apache Spark ETL 任务或 Python shell 任务编写脚本。此外，您还必须完成以下设置任务。
+ 下载 4 个 AWS Python 库，以在蓝图布局脚本中使用。
+ 设置 AWS SDK。
+ 设置 AWS CLI。

## 下载 Python 库
<a name="prereqs-get-libes"></a>

从 GitHub 下载以下库，并将它们安装到您的项目中：
+ [https://github.com/awslabs/aws-glue-blueprint-libs/tree/master/awsglue/blueprint/base\$1resource.py](https://github.com/awslabs/aws-glue-blueprint-libs/tree/master/awsglue/blueprint/base_resource.py)
+ [https://github.com/awslabs/aws-glue-blueprint-libs/tree/master/awsglue/blueprint/workflow.py](https://github.com/awslabs/aws-glue-blueprint-libs/tree/master/awsglue/blueprint/workflow.py)
+ [https://github.com/awslabs/aws-glue-blueprint-libs/tree/master/awsglue/blueprint/crawler.py](https://github.com/awslabs/aws-glue-blueprint-libs/tree/master/awsglue/blueprint/crawler.py)
+ [https://github.com/awslabs/aws-glue-blueprint-libs/tree/master/awsglue/blueprint/job.py](https://github.com/awslabs/aws-glue-blueprint-libs/tree/master/awsglue/blueprint/job.py)

## 设置 AWS Java SDK
<a name="prereqs-java-preview-sdk"></a>

对于 AWS Java SDK，您必须添加 `jar` 文件，其中包含适用于蓝图的 API。

1. 如果您还没有这样做，请设置 AWS SDK for Java。
   + 对于 Java 1.x，请遵循*《适用于 Java 的 AWS SDK 开发人员指南》*中的[设置 适用于 Java 的 AWS SDK](https://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/setup-install.html)。
   + 对于 Java 2.x，请遵循*《AWS SDK for Java 2.x 开发人员指南》*中的[设置 AWS SDK for Java 2.x](https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/setup.html)。

1. 下载客户端 `jar` 文件，该文件拥有访问适用于蓝图的 API 权限。
   + 对于 Java 1.x：s3://awsglue-custom-blueprints-preview-artifacts/awsglue-java-sdk-preview/AWSGlueJavaClient-1.11.x.jar
   + 对于 Java 2.x：s3://awsglue-custom-blueprints-preview-artifacts/awsglue-java-sdk-v2-preview/AwsJavaSdk-Glue-2.0.jar

1. 将客户端 `jar` 添加到 Java 类路径前，覆盖由 AWS Java SDK 提供的 AWS Glue 客户端。

   ```
   export CLASSPATH=<path-to-preview-client-jar>:$CLASSPATH
   ```

1. （可选）使用以下 Java 应用程序测试 SDK。应用程序应输出空列表。

   将 `accessKey` 和 `secretKey` 替换为您的凭证，然后将 `us-east-1` 替换为您的区域。

   ```
   import com.amazonaws.auth.AWSCredentials;
   import com.amazonaws.auth.AWSCredentialsProvider;
   import com.amazonaws.auth.AWSStaticCredentialsProvider;
   import com.amazonaws.auth.BasicAWSCredentials;
   import com.amazonaws.services.glue.AWSGlue;
   import com.amazonaws.services.glue.AWSGlueClientBuilder;
   import com.amazonaws.services.glue.model.ListBlueprintsRequest;
   
   public class App{
       public static void main(String[] args) {
           AWSCredentials credentials = new BasicAWSCredentials("accessKey", "secretKey");
           AWSCredentialsProvider provider = new AWSStaticCredentialsProvider(credentials);
           AWSGlue glue = AWSGlueClientBuilder.standard().withCredentials(provider)
                   .withRegion("us-east-1").build();
           ListBlueprintsRequest request = new ListBlueprintsRequest().withMaxResults(2);
           System.out.println(glue.listBlueprints(request));
       }
   }
   ```

## 设置 AWS Python SDK
<a name="prereqs-python-preview-sdk"></a>

以下步骤假定您的计算机上已安装 Python 版本 2.7 或更高版本，或者版本 3.9 或更高版本。

1. 下载下面的 boto3 wheel 文件。如果提示打开或保存文件，请保存文件 s3://awsglue-custom-blueprints-preview-artifacts/aws-python-sdk-preview/boto3-1.17.31-py2.py3-none-any.whl

1. 下载以下 botocore wheel 文件：s3://awsglue-custom-blueprints-preview-artifacts/aws-python-sdk-preview/botocore-1.20.31-py2.py3-none-any.whl

1. 检查您的 Python 版本。

   ```
   python --version
   ```

1. 根据您的 Python 版本，输入以下命令（适用于 Linux）：
   + 对于 Python 2.7 或更高版本。

     ```
     python3 -m pip install --user virtualenv
     source env/bin/activate
     ```
   + 对于 Python 3.9 或更高版本。

     ```
     python3 -m venv python-sdk-test
     source python-sdk-test/bin/activate
     ```

1. 安装 botocore wheel 文件。

   ```
   python3 -m pip install <download-directory>/botocore-1.20.31-py2.py3-none-any.whl
   ```

1. 安装 boto3 wheel 文件。

   ```
   python3 -m pip install <download-directory>/boto3-1.17.31-py2.py3-none-any.whl
   ```

1. 在 `~/.aws/credentials` 和 `~/.aws/config` 文件中配置您的凭证和默认区域。有关更多信息，请参阅《AWS Command Line Interface 用户指南》**中的[配置 AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-configure.html)。

1. （可选）测试您的设置。以下命令应该返回空列表。

   将 `us-east-1` 替换为您的区域。

   ```
   $ python
   >>> import boto3
   >>> glue = boto3.client('glue', 'us-east-1')
   >>> glue.list_blueprints()
   ```

## 设置预览 AWS CLI
<a name="prereqs-setup-cli"></a>

1. 如果您还没有这样做，请安装和/或更新适用于电脑的 AWS Command Line Interface（AWS CLI）。执行该操作的最简单方法是使用 `pip`，Python 实用安装程序：

   ```
   pip install awscli --upgrade --user
   ```

   您可在此处查找 AWS CLI 的完整安装说明：[安装 AWS Command Line Interface](https://docs.aws.amazon.com/cli/latest/userguide/installing.html)。

1. 下载 AWS CLI wheel 文件：s3://awsglue-custom-blueprints-preview-artifacts/awscli-preview-build/awscli-1.19.31-py2.py3-none-any.whl

1. 安装 AWS CLI wheel 文件。

   ```
   python3 -m pip install awscli-1.19.31-py2.py3-none-any.whl
   ```

1. 运行 `aws configure` 命令。配置 AWS 凭证（包括访问密钥和私有密钥）和 AWS 区域。您可在此处找到有关配置 AWS CLI 的信息：[配置 AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-configure.html)。

1. 测试 AWS CLI。以下命令应该返回空列表。

   将 `us-east-1` 替换为您的区域。

   ```
   aws glue list-blueprints --region us-east-1
   ```

# 编写蓝图代码
<a name="developing-blueprints-code"></a>

您创建的每个蓝图项目必须至少包含以下文件：
+ 定义工作流的 Python 布局脚本。该脚本包含一个函数，用于定义工作流中的实体（任务和爬网程序）以及它们之间的依赖关系。
+ 配置文件 `blueprint.cfg`，它定义了：
  + 工作流布局定义函数的完整路径。
  + 蓝图接受的参数。

**Topics**
+ [创建蓝图布局脚本](developing-blueprints-code-layout.md)
+ [创建配置文件](developing-blueprints-code-config.md)
+ [指定蓝图参数](developing-blueprints-code-parameters.md)

# 创建蓝图布局脚本
<a name="developing-blueprints-code-layout"></a>

蓝图布局脚本必须包含在工作流中生成实体的函数。您可以随意命名此函数。AWS Glue 使用配置文件确定函数的完全限定名称。

您的布局函数执行以下操作：
+ （可选）实例化 `Job` 类来创建 `Job` 对象，并传递诸如 `Command` 和 `Role` 的参数。如果您使用 AWS Glue 控制台或 API 来创建任务，这些是您要指定的任务属性。
+ （可选）实例化 `Crawler` 类来创建 `Crawler` 对象，并传递名称、角色和目标参数。
+ 要指示对象（工作流实体）之间的依赖项，请将 `DependsOn` 和 `WaitForDependencies` 等其他参数传递到 `Job()` 和 `Crawler()`。此部分的后面将说明这些参数。
+ 实例化 `Workflow` 类以创建返回到 AWS Glue 的工作流对象，传递 `Name` 参数、`Entities` 参数和可选的 `OnSchedule` 参数。`Entities` 参数指定要包含在工作流中的所有任务和爬网程序。如需了解如何构建 `Entities` 对象，请参阅此部分后面的示例项目。
+ 返回 `Workflow` 对象。

有关 `Job`、`Crawler` 和 `Workflow` 类的定义，请参阅[AWS Glue 蓝图类参考](developing-blueprints-code-classes.md)。

该布局函数接受以下输入参数。


| 参数 | 说明 | 
| --- | --- | 
| user\$1params | 蓝图参数名称和值的 Python 词典。有关更多信息，请参阅 [指定蓝图参数](developing-blueprints-code-parameters.md)。 | 
| system\$1params | Python 词典包含两个属性：region 和 accountId。 | 

这里是一个文件名为 `Layout.py` 的简单布局生成器脚本：

```
import argparse
import sys
import os
import json
from awsglue.blueprint.workflow import *
from awsglue.blueprint.job import *
from awsglue.blueprint.crawler import *


def generate_layout(user_params, system_params):

    etl_job = Job(Name="{}_etl_job".format(user_params['WorkflowName']),
                  Command={
                      "Name": "glueetl",
                      "ScriptLocation": user_params['ScriptLocation'],
                      "PythonVersion": "2"
                  },
                  Role=user_params['PassRole'])
    post_process_job = Job(Name="{}_post_process".format(user_params['WorkflowName']),
                            Command={
                                "Name": "pythonshell",
                                "ScriptLocation": user_params['ScriptLocation'],
                                "PythonVersion": "2"
                            },
                            Role=user_params['PassRole'],
                            DependsOn={
                                etl_job: "SUCCEEDED"
                            },
                            WaitForDependencies="AND")
    sample_workflow = Workflow(Name=user_params['WorkflowName'],
                            Entities=Entities(Jobs=[etl_job, post_process_job]))
    return sample_workflow
```

示例脚本会导入所需的蓝图库，并包含 `generate_layout` 函数，用于生成包含两个任务的工作流。这是一个非常简单的脚本。更复杂的脚本可以使用额外的逻辑和参数来生成具有许多任务和爬网程序（甚至可变数量的任务和爬网程序）的工作流。

## 使用 DependsOn 参数
<a name="developing-blueprints-code-layout-depends-on"></a>

`DependsOn` 参数是此实体对工作流中其他实体所具有的依赖关系的字典表示形式。格式如下。

```
DependsOn = {dependency1 : state, dependency2 : state, ...}
```

此字典中的密钥表示实体的对象引用，而不是名称，而值是对应于要监视的状态的字符串。AWS Glue 推断合适的触发器。有关有效状态，请参阅[条件结构](https://docs.aws.amazon.com/glue/latest/dg/aws-glue-api-jobs-trigger.html#aws-glue-api-jobs-trigger-Condition)。

例如，任务可能依赖于爬网程序的成功完成。如果您定义了名为 `crawler2` 的爬网程序对象，如下所示：

```
crawler2 = Crawler(Name="my_crawler", ...)
```

然后一个依赖于 `crawler2` 的对象将包含一个构造函数参数，例如：

```
DependsOn = {crawler2 : "SUCCEEDED"}
```

例如：

```
job1 = Job(Name="Job1", ..., DependsOn = {crawler2 : "SUCCEEDED", ...})
```

如果实体省略了 `DependsOn`，则该实体依赖于工作流启动触发器。

## 使用 WaitForDependencies 参数
<a name="developing-blueprints-code-layout-wait-for-dependencies"></a>

`WaitForDependencies` 参数定义任务或爬网程序实体是应该等待它依赖的*全部*实体完成，还是等待*任意*实体完成。

允许的值为“`AND`”或”`ANY`“。

## 使用 OnSchedule 参数
<a name="developing-blueprints-code-layout-on-schedule"></a>

`Workflow` 类构造函数的 `OnSchedule` 参数是 `cron` 表达式，用于定义工作流的启动触发器。

如果指定此参数，AWS Glue 创建一个带有相应计划的计划触发器。如果未指定启动触发器，则工作流的启动触发器是按需触发器。

# 创建配置文件
<a name="developing-blueprints-code-config"></a>

蓝图配置文件是必需的文件，用于定义生成工作流的脚本入口点以及蓝图接受的参数。文件必须命名为 `blueprint.cfg`。

下面是示例配置文件。

```
{
    "layoutGenerator": "DemoBlueprintProject.Layout.generate_layout",
    "parameterSpec" : {
           "WorkflowName" : {
                "type": "String",
                "collection": false
           },
           "WorkerType" : {
                "type": "String",
                "collection": false,
                "allowedValues": ["G1.X", "G2.X"],
                "defaultValue": "G1.X"
           },
           "Dpu" : {
                "type" : "Integer",
                "allowedValues" : [2, 4, 6],
                "defaultValue" : 2
           },
           "DynamoDBTableName": {
                "type": "String",
                "collection" : false
           },
           "ScriptLocation" : {
                "type": "String",
                "collection": false
    	}
    }
}
```

`layoutGenerator` 属性指定生成布局的脚本中函数的完全限定名称。

`parameterSpec` 属性指定此蓝图接受的参数。有关更多信息，请参阅 [指定蓝图参数](developing-blueprints-code-parameters.md)。

**重要**  
配置文件必须包含工作流名称作为蓝图参数，或者必须在布局脚本中生成唯一的工作流名称。

# 指定蓝图参数
<a name="developing-blueprints-code-parameters"></a>

配置文件包含 `parameterSpec` JSON 对象中的蓝图参数规范。`parameterSpec` 包含一个或多个参数对象。

```
"parameterSpec": {
    "<parameter_name>": {
      "type": "<parameter-type>",
      "collection": true|false, 
      "description": "<parameter-description>",
      "defaultValue": "<default value for the parameter if value not specified>"
      "allowedValues": "<list of allowed values>" 
    },
    "<parameter_name>": {    
       ...
    }
  }
```

以下是编码每个参数对象的规则：
+ 参数名称和 `type` 是必需的。所有其他属性均为可选属性。
+ 如果您指定 `defaultValue` 属性，则该参数是可选的。否则，参数是必需参数，并且从蓝图创建工作流的数据分析员必须为其提供值。
+ 如果您将 `collection` 属性设置为 `true`，参数可以采用值的集合。集合可以是任意数据类型。
+ 如果您指定 `allowedValues`，则 AWS Glue 控制台显示一个值下拉列表，供数据分析人员在从蓝图创建工作流时进行选择。

以下是 `type` 允许的值：


| 参数数据类型 | 备注 | 
| --- | --- | 
| String | - | 
| Integer | - | 
| Double | - | 
| Boolean | 可能的值为 true 和 false。AWS Glue 控制台的 Create a workflow from <blueprint>（从 <蓝图> 创建工作流）页面生成的一个复选框。 | 
| S3Uri | 从 s3:// 开始完成 Amazon S3 路径。Create a workflow from <blueprint> (从 <blueprint> 创建工作流) 页面生成文本字段和 Browse (浏览) 按钮。 | 
| S3Bucket | 仅限 Amazon S3 存储桶名称。在 Create a workflow from <blueprint> (从 <blueprint> 创建工作流) 页生成存储桶选取器。 | 
| IAMRoleArn | AWS Identity and Access Management（IAM）角色的 Amazon Resource Name（ARN）。在 Create a workflow from <blueprint> (从 <blueprint> 创建工作流) 页生成角色选取器。 | 
| IAMRoleName | IAM 角色的名称。在 Create a workflow from <blueprint> (从 <blueprint> 创建工作流) 页生成角色选取器。 | 

# 示例蓝图项目
<a name="developing-blueprints-sample"></a>

数据格式转换是一种常见的提取、转换和加载（ETL）使用案例。在典型的分析工作负载中，基于列的文件格式（如 Parquet 或 ORC）优先于 CSV 或 JSON 等文本格式。此示例蓝图使您能够将 CSV/JSON/ 等格式的数据转换为 Amazon S3 文件的 Parquet。

此蓝图采用由蓝图参数定义的 S3 路径列表，将数据转换为 Parquet 格式，并将其写入另一个蓝图参数指定的 S3 位置。布局脚本为每个路径创建一个爬网程序和任务。布局脚本还将 `Conversion.py` 中的 ETL 脚本上载到另一个蓝图参数指定的 S3 存储桶。然后，布局脚本将上载的脚本指定为每个任务的 ETL 脚本。项目的 ZIP 格式归档文件包含布局脚本、ETL 脚本和蓝图配置文件。

有关更多示例蓝图项目的信息，请参阅[蓝图示例](developing-blueprints-samples.md)。

以下是布局脚本，位于文件 `Layout.py`。

```
from awsglue.blueprint.workflow import *
from awsglue.blueprint.job import *
from awsglue.blueprint.crawler import *
import boto3

s3_client = boto3.client('s3')

# Ingesting all the S3 paths as Glue table in parquet format
def generate_layout(user_params, system_params):
    #Always give the full path for the file
    with open("ConversionBlueprint/Conversion.py", "rb") as f:
        s3_client.upload_fileobj(f, user_params['ScriptsBucket'], "Conversion.py")
    etlScriptLocation = "s3://{}/Conversion.py".format(user_params['ScriptsBucket'])    
    crawlers = []
    jobs = []
    workflowName = user_params['WorkflowName']
    for path in user_params['S3Paths']:
      tablePrefix = "source_" 
      crawler = Crawler(Name="{}_crawler".format(workflowName),
                        Role=user_params['PassRole'],
                        DatabaseName=user_params['TargetDatabase'],
                        TablePrefix=tablePrefix,
                        Targets= {"S3Targets": [{"Path": path}]})
      crawlers.append(crawler)
      transform_job = Job(Name="{}_transform_job".format(workflowName),
                         Command={"Name": "glueetl",
                                  "ScriptLocation": etlScriptLocation,
                                  "PythonVersion": "3"},
                         Role=user_params['PassRole'],
                         DefaultArguments={"--database_name": user_params['TargetDatabase'],
                                           "--table_prefix": tablePrefix,
                                           "--region_name": system_params['region'],
                                           "--output_path": user_params['TargetS3Location']},
                         DependsOn={crawler: "SUCCEEDED"},
                         WaitForDependencies="AND")
      jobs.append(transform_job)
    conversion_workflow = Workflow(Name=workflowName, Entities=Entities(Jobs=jobs, Crawlers=crawlers))
    return conversion_workflow
```

以下是相应的蓝图配置文件 `blueprint.cfg`。

```
{
    "layoutGenerator": "ConversionBlueprint.Layout.generate_layout",
    "parameterSpec" : {
        "WorkflowName" : {
            "type": "String",
            "collection": false,
            "description": "Name for the workflow."
        },
        "S3Paths" : {
            "type": "S3Uri",
            "collection": true,
            "description": "List of Amazon S3 paths for data ingestion."
        },
        "PassRole" : {
            "type": "IAMRoleName",
            "collection": false,
            "description": "Choose an IAM role to be used in running the job/crawler"
        },
        "TargetDatabase": {
            "type": "String",
            "collection" : false,
            "description": "Choose a database in the Data Catalog."
        },
        "TargetS3Location": {
            "type": "S3Uri",
            "collection" : false,
            "description": "Choose an Amazon S3 output path: ex:s3://<target_path>/."
        },
        "ScriptsBucket": {
            "type": "S3Bucket",
            "collection": false,
            "description": "Provide an S3 bucket name(in the same AWS Region) to store the scripts."
        }
    }
}
```

文件 `Conversion.py` 中的以下脚本是上传的 ETL 脚本。请注意，它会在转换过程中保留分区方案。

```
import sys
from pyspark.sql.functions import *
from pyspark.context import SparkContext
from awsglue.transforms import *
from awsglue.context import GlueContext
from awsglue.job import Job
from awsglue.utils import getResolvedOptions
import boto3

args = getResolvedOptions(sys.argv, [
    'JOB_NAME',
    'region_name',
    'database_name',
    'table_prefix',
    'output_path'])
databaseName = args['database_name']
tablePrefix = args['table_prefix']
outputPath = args['output_path']

glue = boto3.client('glue', region_name=args['region_name'])

glue_context = GlueContext(SparkContext.getOrCreate())
spark = glue_context.spark_session
job = Job(glue_context)
job.init(args['JOB_NAME'], args)

def get_tables(database_name, table_prefix):
    tables = []
    paginator = glue.get_paginator('get_tables')
    for page in paginator.paginate(DatabaseName=database_name, Expression=table_prefix+"*"):
        tables.extend(page['TableList'])
    return tables

for table in get_tables(databaseName, tablePrefix):
    tableName = table['Name']
    partitionList = table['PartitionKeys']
    partitionKeys = []
    for partition in partitionList:
        partitionKeys.append(partition['Name'])

    # Create DynamicFrame from Catalog
    dyf = glue_context.create_dynamic_frame.from_catalog(
        name_space=databaseName,
        table_name=tableName,
        additional_options={
            'useS3ListImplementation': True
        },
        transformation_ctx='dyf'
    )

    # Resolve choice type with make_struct
    dyf = ResolveChoice.apply(
        frame=dyf,
        choice='make_struct',
        transformation_ctx='resolvechoice_' + tableName
    )

    # Drop null fields
    dyf = DropNullFields.apply(
        frame=dyf,
        transformation_ctx="dropnullfields_" + tableName
    )

    # Write DynamicFrame to S3 in glueparquet
    sink = glue_context.getSink(
        connection_type="s3",
        path=outputPath,
        enableUpdateCatalog=True,
        partitionKeys=partitionKeys
    )
    sink.setFormat("glueparquet")

    sink.setCatalogInfo(
        catalogDatabase=databaseName,
        catalogTableName=tableName[len(tablePrefix):]
    )
    sink.writeFrame(dyf)

job.commit()
```

**注意**  
只能提供两个 Amazon S3 路径作为对示例蓝图的输入。这是因为 AWS Glue 触发器仅限于调用两个爬网程序操作。

# 测试蓝图
<a name="developing-blueprints-testing"></a>

在开发代码时，应执行本地测试以验证工作流布局是否正确。

本地测试不会生成 AWS Glue 任务、爬网程序或触发器。相反，您可以在本地运行布局脚本并使用 `to_json()` 和 `validate()` 方法打印对象并查找错误。这些方法在库中定义的所有三个类中都可用。

可通过两种方式处理 AWS Glue 传递到您的布局函数的 `user_params` 和 `system_params` 参数。您的测试平台代码可以创建示例蓝图参数值的字典，并将其作为 `user_params` 参数传递到布局函数。或者，您可以移除对 `user_params` 的引用并使用硬编码字符串替代他们。

如果您的代码使用 `system_params` 参数中的 `region` 和 `accountId` 属性，您可以传入所拥有的 `system_params` 字典。

**要测试蓝图**

1. 在包含库的目录中启动 Python 解释器，或将蓝图文件和提供的库加载到首选的集成开发环境（IDE）中。

1. 确保您的代码导入提供的库。

1. 将代码添加到布局函数以调用在任何实体上或在 `Workflow` 对象上的 `validate()` 或 `to_json()`。例如，如果您的代码创建了一个名为 `mycrawler` 的 `Crawler` 对象，您可以调用 `validate()`，如下所示。

   ```
   mycrawler.validate()
   ```

   您可以打印 `mycrawler`，如下所示：

   ```
   print(mycrawler.to_json())
   ```

   如果您对一个对象调用 `to_json`，则不需要同时调用 `validate()`，因为 ` to_json()` 调用了 `validate()`。

   对工作流对象调用这些方法非常有用。假设您的脚本命名工作流对象 `my_workflow`，验证并打印工作流对象，如下所示。

   ```
   print(my_workflow.to_json())
   ```

   有关 `to_json()` 和 `validate()` 的更多信息，请参阅 [类方法](developing-blueprints-code-classes.md#developing-blueprints-code-methods)。

   您还可以导入 `pprint` 并美观地打印列工作流对象，如本部分后面的示例所示。

1. 运行代码，修复错误，最后删除对 `validate()` 或者 `to_json()` 的调用。

**Example**  
以下示例说明如何构建示例蓝图参数字典并将其作为 `user_params` 参数传入布局函数 `generate_compaction_workflow`。它还说明了如何美观地打印生成的工作流对象。  

```
from pprint import pprint
from awsglue.blueprint.workflow import *
from awsglue.blueprint.job import *
from awsglue.blueprint.crawler import *
 
USER_PARAMS = {"WorkflowName": "compaction_workflow",
               "ScriptLocation": "s3://amzn-s3-demo-bucket/scripts/threaded-compaction.py",
               "PassRole": "arn:aws:iam::111122223333:role/GlueRole-ETL",
               "DatabaseName": "cloudtrial",
               "TableName": "ct_cloudtrail",
               "CoalesceFactor": 4,
               "MaxThreadWorkers": 200}
 
 
def generate_compaction_workflow(user_params: dict, system_params: dict) -> Workflow:
    compaction_job = Job(Name=f"{user_params['WorkflowName']}_etl_job",
                         Command={"Name": "glueetl",
                                  "ScriptLocation": user_params['ScriptLocation'],
                                  "PythonVersion": "3"},
                         Role="arn:aws:iam::111122223333:role/AWSGlueServiceRoleDefault",
                         DefaultArguments={"DatabaseName": user_params['DatabaseName'],
                                           "TableName": user_params['TableName'],
                                           "CoalesceFactor": user_params['CoalesceFactor'],
                                           "max_thread_workers": user_params['MaxThreadWorkers']})
 
    catalog_target = {"CatalogTargets": [{"DatabaseName": user_params['DatabaseName'], "Tables": [user_params['TableName']]}]}
 
    compacted_files_crawler = Crawler(Name=f"{user_params['WorkflowName']}_post_crawl",
                                      Targets = catalog_target,
                                      Role=user_params['PassRole'],
                                      DependsOn={compaction_job: "SUCCEEDED"},
                                      WaitForDependencies="AND",
                                      SchemaChangePolicy={"DeleteBehavior": "LOG"})
 
    compaction_workflow = Workflow(Name=user_params['WorkflowName'],
                                   Entities=Entities(Jobs=[compaction_job],
                                                     Crawlers=[compacted_files_crawler]))
    return compaction_workflow
 
generated = generate_compaction_workflow(user_params=USER_PARAMS, system_params={})
gen_dict = generated.to_json()
 
pprint(gen_dict)
```

# 发布蓝图
<a name="developing-blueprints-publishing"></a>

开发蓝图后，您必须将其上传到 Amazon S3 中。您必须对用于发布蓝图的 Amazon S3 存储桶具有写入权限。您还必须确保注册蓝图的 AWS Glue 管理员具有 Amazon S3 存储桶的读取权限。如需用于角色的 AWS Identity and Access Management（IAM）权限策略和用于 AWS Glue 蓝图的角色方面的相关建议，请参阅[AWS Glue 蓝图的角色权限](blueprints-personas-permissions.md)。

**要发布蓝图**

1. 创建必要的脚本、资源和蓝图配置文件。

1. 将所有文件添加到 zip 格式归档中，并将 zip 格式文件上传到 Amazon S3。对与用户将注册和运行蓝图的区域，使用与其相同区域的 S3 存储桶。

   您可以使用以下命令从命令行中创建 zip 格式文件。

   ```
   zip -r folder.zip folder
   ```

1. 添加一个存储桶策略，该策略将授予 AWS 所需账户的读取权限。以下是策略示例。

------
#### [ JSON ]

****  

   ```
   {
     "Version":"2012-10-17",		 	 	 
     "Statement": [
       {
         "Effect": "Allow",
         "Principal": {
           "AWS": "arn:aws:iam::111122223333:root"
         },
         "Action": "s3:GetObject",
         "Resource": "arn:aws:s3:::my-blueprints/*"
       }
     ]
   }
   ```

------

1. 对 AWS Glue 管理员或对任何注册蓝图的人员授予 Amazon S3 存储桶的 IAM `s3:GetObject` 访问权限。有关授予管理员的示例策略，请参阅[蓝图的 AWS Glue 管理员权限](blueprints-personas-permissions.md#bp-persona-admin)。

完成蓝图的本地测试后，您可能还需要在 AWS Glue 上测试蓝图。要在 AWS Glue 上测试蓝图，则必须注册。您可以使用 IAM 授权或使用单独的测试账户来限制可以查看已注册蓝图的人员。

**另请参阅：**  
[在 AWS Glue 中注册蓝图](registering-blueprints.md)

# AWS Glue 蓝图类参考
<a name="developing-blueprints-code-classes"></a>

适用于 AWS Glue 蓝图的库定义了在工作流布局脚本中使用的三个类：`Job`、`Crawler` 和 `Workflow`。

**Topics**
+ [作业类](#developing-blueprints-code-jobclass)
+ [爬网程序类](#developing-blueprints-code-crawlerclass)
+ [工作流类](#developing-blueprints-code-workflowclass)
+ [类方法](#developing-blueprints-code-methods)

## 作业类
<a name="developing-blueprints-code-jobclass"></a>

`Job` 类表示一个 AWS Glue ETL 任务。

**强制构造函数参数**  
以下是适用于 `Job` 类的强制构造函数参数。


| 参数名 | Type | 说明 | 
| --- | --- | --- | 
| Name | str | 要分配给任务的名称。AWS Glue 将随机生成的后缀添加到名称中，以区分其他蓝图运行创建的任务。 | 
| Role | str | 任务在执行时应担任角色的 Amazon Resource Name（ARN）。 | 
| Command | dict | 任务命令，如指定在 API 文档中的 [JobCommand 结构](aws-glue-api-jobs-job.md#aws-glue-api-jobs-job-JobCommand)。 | 

**可选构造函数参数**  
以下是适用于 `Job` 类的可选构造函数参数。


| 参数名 | Type | 说明 | 
| --- | --- | --- | 
| DependsOn | dict | 任务所依赖的工作流实体的列表。有关更多信息，请参阅 [使用 DependsOn 参数](developing-blueprints-code-layout.md#developing-blueprints-code-layout-depends-on)。 | 
| WaitForDependencies | str | 指示任务是应该等待它依赖的全部实体完成，还是等待任意实体完成。有关更多信息，请参阅 [使用 WaitForDependencies 参数](developing-blueprints-code-layout.md#developing-blueprints-code-layout-wait-for-dependencies)。如果任务仅依赖于一个实体，则省略。 | 
| （任务属性） | - | 任何任务属性都在 AWS Glue API 文档中的[作业结构](aws-glue-api-jobs-job.md#aws-glue-api-jobs-job-Job)内列出（CreatedOn 和 LastModifiedOn 除外）。 | 

## 爬网程序类
<a name="developing-blueprints-code-crawlerclass"></a>

`Crawler` 类表示一个 AWS Glue 爬网程序。

**强制构造函数参数**  
以下是适用于 `Crawler` 类的强制构造函数参数。


| 参数名 | Type | 说明 | 
| --- | --- | --- | 
| Name | str | 要分配给爬网程序的名称。AWS Glue 将随机生成的后缀添加到名称中，以区分其他蓝图运行创建的爬网程序。 | 
| Role | str | 运行时爬网程序应担任角色的 ARN。 | 
| Targets | dict | 要网络爬取的目标集合。Targets 类构造函数在 API 文档中的[CrawlerTargets 结构](aws-glue-api-crawler-crawling.md#aws-glue-api-crawler-crawling-CrawlerTargets)内定义。全部 Targets 构造函数参数是可选的，但您必须至少传递一个。 | 

**可选构造函数参数**  
以下是适用于 `Crawler` 类的可选构造函数参数。


| 参数名 | Type | 说明 | 
| --- | --- | --- | 
| DependsOn | dict | 爬网程序所依赖的工作流实体列表。有关更多信息，请参阅 [使用 DependsOn 参数](developing-blueprints-code-layout.md#developing-blueprints-code-layout-depends-on)。 | 
| WaitForDependencies | str | 指示爬网程序是应该等待它依赖的全部实体完成，还是等待任意实体完成。有关更多信息，请参阅 [使用 WaitForDependencies 参数](developing-blueprints-code-layout.md#developing-blueprints-code-layout-wait-for-dependencies)。如果爬网程序仅依赖于一个实体，则省略。 | 
| （爬网程序属性） | - | 所有爬网程序属性都在 AWS Glue API 文档中的[Crawler 结构](aws-glue-api-crawler-crawling.md#aws-glue-api-crawler-crawling-Crawler)内列出，以下属性例外：[\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/zh_cn/glue/latest/dg/developing-blueprints-code-classes.html) | 

## 工作流类
<a name="developing-blueprints-code-workflowclass"></a>

`Workflow` 类表示一个 AWS Glue 工作流。工作流布局脚本返回 `Workflow` 对象。AWS Glue 基于此对象创建工作流。

**强制构造函数参数**  
以下是适用于 `Workflow` 类的强制构造函数参数。


| 参数名 | Type | 说明 | 
| --- | --- | --- | 
| Name | str | 要向工作流分配的名称。 | 
| Entities | Entities | 要包含在工作流中的实体（任务和爬网程序）的集合。Entities 类构造函数接受 Jobs 参数（Job 对象列表）和 Crawlers 参数（Crawler 对象列表）。 | 

**可选构造函数参数**  
以下是适用于 `Workflow` 类的可选构造函数参数。


| 参数名 | Type | 说明 | 
| --- | --- | --- | 
| Description | str | 请参阅 [Workflow 结构](aws-glue-api-workflow.md#aws-glue-api-workflow-Workflow)。 | 
| DefaultRunProperties | dict | 请参阅 [Workflow 结构](aws-glue-api-workflow.md#aws-glue-api-workflow-Workflow)。 | 
| OnSchedule | str | cron 表达式 | 

## 类方法
<a name="developing-blueprints-code-methods"></a>

所有三个类都包括以下方法。

**validate()**  
验证对象的属性，如果发现错误，则输出消息并退出。如果没有错误，则不生成输出。对于 `Workflow` 类，会在工作流中的每个实体上调用自己。

**to\$1json()**  
将对象序列化为 JSON。还调用 `validate()`。对于 `Workflow` 类，JSON 对象包括任务和爬网程序列表，以及由任务和爬网程序依赖关系规范生成的触发器列表。

# 蓝图示例
<a name="developing-blueprints-samples"></a>

有许多示例蓝图项目可用于 [AWS Glue 蓝图 Github 存储库](https://github.com/awslabs/aws-glue-blueprint-libs/tree/master/samples)。这些示例仅供参考，不能用于生产。

示例项目的标题是：
+ 压缩：此蓝图创建一个任务，根据所需的文件大小将输入文件压缩到更大的数据块中。
+ 转换：此蓝图将各种标准文件格式的输入文件转换为 Apache Parquet 格式，该格式针对分析工作负载进行了优化。
+ 网络爬取 Amazon S3 位置：此蓝图对多个 Amazon S3 位置进行网络爬取，将元数据表添加到数据目录。
+ 自定义到数据目录的连接：此蓝图使用 AWS Glue 自定义连接器访问数据存储，读取记录，并根据记录架构填充 AWS Glue 数据目录中的表定义。
+ 编码：此蓝图将您的非 UTF 文件转换为 UTF 编码的文件。
+ 分区：此蓝图创建一个分区任务，根据特定的分区键将输出文件放入分区。
+ 将 Amazon S3 数据导入到 DynamoDB 表中：此蓝图将数据从 Amazon S3 导入到 DynamoDB 表中。
+ 所管理的标准表：此蓝图将 AWS Glue 数据目录表导入到 Lake Formation 表中。