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

feat: JSON schema title is now supported in configuration and stream properties #2712

Merged
merged 2 commits into from
Oct 9, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ class {{ cookiecutter.name }}Mapper(InlineMapper):
th.Property(
"example_config",
th.StringType,
title="Example Configuration",
description="An example config, replace or remove based on your needs.",
),
).to_dict()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,14 @@ class Tap{{ cookiecutter.source_name }}({{ 'SQL' if cookiecutter.stream_type ==
th.StringType,
required=True,
secret=True, # Flag config as protected.
title="Auth Token",
description="The token to authenticate against the API service",
),
th.Property(
"project_ids",
th.ArrayType(th.StringType),
required=True,
title="Project IDs",
description="Project IDs to replicate",
),
th.Property(
Expand All @@ -47,6 +49,7 @@ class Tap{{ cookiecutter.source_name }}({{ 'SQL' if cookiecutter.stream_type ==
th.Property(
"api_url",
th.StringType,
title="API URL",
default="https://api.mysample.com",
description="The url for the API service",
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,23 +23,27 @@ class Target{{ cookiecutter.destination_name }}({{ target_class }}):
"sqlalchemy_url",
th.StringType,
secret=True, # Flag config as protected.
title="SQLAlchemy URL",
description="SQLAlchemy connection string",
),
{%- else %}
th.Property(
"filepath",
th.StringType,
title="Output File Path",
description="The path to the target output file",
),
th.Property(
"file_naming_scheme",
th.StringType,
title="File Naming Scheme",
description="The scheme with which output files will be named",
),
th.Property(
"auth_token",
th.StringType,
secret=True, # Flag config as protected.
title="Auth Token",
description="The path to the target output file",
),
{%- endif %}
Expand Down
7 changes: 4 additions & 3 deletions docs/guides/config-schema.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ class MyTap(Tap):
name = "my-tap"

config_jsonschema = th.PropertiesList(
th.Property("api_key", th.StringType, required=True),
th.Property("base_url", th.StringType, default="https://api.example.com"),
th.Property("start_date", th.DateTimeType),
th.Property("api_key", th.StringType, required=True, title="API Key"),
th.Property("base_url", th.StringType, default="https://api.example.com", title="Base URL"),
th.Property("start_date", th.DateTimeType, title="Start Date"),
).to_dict()
```

Expand All @@ -29,6 +29,7 @@ Explanation of the configuration schema defined above:
- The `th.StringType`, `th.DateTimeType`, etc. helpers are used to define the type of the property.
- The `required` attribute is used to mark a property as required. The tap will throw an error if the user does not provide a value for a required property.
- The `default` attribute is used to provide a default value for a property. The tap will use this if the user does not provide a value, so this can be accessed in the tap or streams with square bracket syntax, i.e. `self.config["base_url"]`.
- The `title` attribute is used to provide a human-readable title for the property.
- The `to_dict()` method is used to convert the JSON object to a Python dictionary.

See the full reference for the [typing module](../typing.rst) for more information on how to define a configuration schema.
645 changes: 384 additions & 261 deletions poetry.lock

Large diffs are not rendered by default.

13 changes: 13 additions & 0 deletions singer_sdk/contrib/filesystem/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,35 +12,41 @@
"host",
th.StringType,
required=True,
title="FTP Host",
description="FTP server host",
),
th.Property(
"port",
th.IntegerType,
default=21,
title="FTP Port",
description="FTP server port",
),
th.Property(
"username",
th.StringType,
title="FTP Username",
description="FTP username",
),
th.Property(
"password",
th.StringType,
secret=True,
title="FTP Password",
description="FTP password",
),
th.Property(
"timeout",
th.IntegerType,
default=60,
title="Timeout",
description="Timeout of the FTP connection in seconds",
),
th.Property(
"encoding",
th.StringType,
default="utf-8",
title="Encoding",
description="FTP server encoding",
),
),
Expand All @@ -56,6 +62,7 @@
"host",
th.StringType,
required=True,
title="SFTP Host",
description="SFTP server host",
),
th.Property(
Expand All @@ -65,33 +72,39 @@
"port",
th.IntegerType,
default=22,
title="SFTP Port",
description="SFTP server port",
),
th.Property(
"username",
th.StringType,
required=True,
title="SFTP Username",
description="SFTP username",
),
th.Property(
"password",
th.StringType,
secret=True,
title="SFTP Password",
description="SFTP password",
),
th.Property(
"pkey",
th.StringType,
secret=True,
title="SFTP Private Key",
description="Private key",
),
th.Property(
"timeout",
th.IntegerType,
default=60,
title="Timeout",
description="Timeout of the SFTP connection in seconds",
),
),
title="SSH Connection Settings",
description="SSH connection settings",
),
),
Expand Down
4 changes: 4 additions & 0 deletions singer_sdk/contrib/filesystem/tap.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,18 +36,21 @@ class ReadMode(str, enum.Enum):
required=True,
default="local",
allowed_values=["local", "ftp", "sftp"],
title="Filesystem",
description="The filesystem to use.",
),
th.Property(
"path",
th.StringType,
required=True,
title="Directory Path",
description="Path to the directory where the files are stored.",
),
th.Property(
"read_mode",
th.StringType,
required=True,
title="Read Mode",
description=(
"Use `one_stream_per_file` to read each file as a separate stream, or "
"`merge` to merge all files into a single stream."
Expand All @@ -59,6 +62,7 @@ class ReadMode(str, enum.Enum):
th.StringType,
required=True,
default=DEFAULT_MERGE_STREAM_NAME,
title="Stream Name (Merge Mode)",
description="Name of the stream to use when `read_mode` is `merge`.",
),
filesystem_config.FTP,
Expand Down
23 changes: 21 additions & 2 deletions singer_sdk/helpers/capabilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
Property(
"stream_maps",
ObjectType(),
title="Stream Maps",
description=(
"Config object for stream maps capability. "
"For more information check out "
Expand All @@ -35,6 +36,7 @@
Property(
"stream_map_config",
ObjectType(),
title="User Stream Map Configuration",
description="User-defined config values to be used within map expressions.",
),
Property(
Expand All @@ -43,6 +45,7 @@
Property(
"seed",
OneOf(NumberType, StringType, BooleanType),
title="Faker Seed",
description=(
"Value to seed the Faker generator for deterministic output: "
"https://faker.readthedocs.io/en/master/#seeding-the-generator"
Expand All @@ -51,12 +54,14 @@
Property(
"locale",
OneOf(StringType, ArrayType(StringType)),
title="Faker Locale",
description=(
"One or more LCID locale strings to produce localized output for: "
"https://faker.readthedocs.io/en/master/#localization"
),
),
),
title="Faker Configuration",
description=(
"Config for the [`Faker`](https://faker.readthedocs.io/en/master/) "
"instance variable `fake` used within map expressions. Only applicable if "
Expand All @@ -69,6 +74,7 @@
Property(
"flattening_enabled",
BooleanType(),
title="Enable Schema Flattening",
description=(
"'True' to enable schema flattening and automatically expand nested "
"properties."
Expand All @@ -77,44 +83,52 @@
Property(
"flattening_max_depth",
IntegerType(),
title="Max Flattening Depth",
description="The max depth to flatten schemas.",
),
).to_dict()
BATCH_CONFIG = PropertiesList(
Property(
"batch_config",
description="",
title="Batch Configuration",
description="Configuration for BATCH message capabilities.",
wrapped=ObjectType(
Property(
"encoding",
title="Batch Encoding Configuration",
description="Specifies the format and compression of the batch files.",
wrapped=ObjectType(
Property(
"format",
StringType,
allowed_values=["jsonl", "parquet"],
title="Batch Encoding Format",
description="Format to use for batch files.",
),
Property(
"compression",
StringType,
allowed_values=["gzip", "none"],
title="Batch Compression Format",
description="Compression format to use for batch files.",
),
),
),
Property(
"storage",
title="Batch Storage Configuration",
description="Defines the storage layer to use when writing batch files",
wrapped=ObjectType(
Property(
"root",
StringType,
title="Batch Storage Root",
description="Root path to use when writing batch files.",
),
Property(
"prefix",
StringType,
title="Batch Storage Prefix",
description="Prefix to use when writing batch files.",
),
),
Expand All @@ -126,20 +140,23 @@
Property(
"default_target_schema",
StringType(),
title="Default Target Schema",
description="The default target database schema name to use for all streams.",
),
).to_dict()
ADD_RECORD_METADATA_CONFIG = PropertiesList(
Property(
"add_record_metadata",
BooleanType(),
description="Add metadata to records.",
title="Add Record Metadata",
description="Whether to add metadata fields to records.",
),
).to_dict()
TARGET_HARD_DELETE_CONFIG = PropertiesList(
Property(
"hard_delete",
BooleanType(),
title="Hard Delete",
description="Hard delete records.",
default=False,
),
Expand All @@ -148,6 +165,7 @@
Property(
"validate_records",
BooleanType(),
title="Validate Records",
description="Whether to validate the schema of the incoming streams.",
default=True,
),
Expand All @@ -156,6 +174,7 @@
Property(
"batch_size_rows",
IntegerType,
title="Batch Size Rows",
description="Maximum number of rows in each batch.",
),
).to_dict()
Expand Down
15 changes: 14 additions & 1 deletion singer_sdk/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -619,7 +619,7 @@ class Property(JSONTypeHelper[T], t.Generic[T]):
"""Generic Property. Should be nested within a `PropertiesList`."""

# TODO: Make some of these arguments keyword-only. This is a breaking change.
def __init__(
def __init__( # noqa: PLR0913
self,
name: str,
wrapped: JSONTypeHelper[T] | type[JSONTypeHelper[T]],
Expand All @@ -631,6 +631,7 @@ def __init__(
examples: list[T] | None = None,
*,
nullable: bool | None = None,
title: str | None = None,
) -> None:
"""Initialize Property object.

Expand All @@ -652,6 +653,7 @@ def __init__(
examples: Optional. A list of one or more sample values. These may be
displayed to the user as hints of the expected format of inputs.
nullable: If True, the property may be null.
title: Optional. A short, human-readable title for the property.
"""
self.name = name
self.wrapped = wrapped
Expand All @@ -662,6 +664,7 @@ def __init__(
self.allowed_values = allowed_values or None
self.examples = examples or None
self.nullable = nullable
self.title = title

@property
def type_dict(self) -> dict: # type: ignore[override]
Expand Down Expand Up @@ -690,8 +693,18 @@ def to_dict(self) -> dict:

Returns:
A JSON Schema dictionary describing the object.

Examples:
>>> p = Property("name", StringType, required=True)
>>> print(p.to_dict())
{'name': {'type': ['string']}}
>>> p = Property("name", StringType, required=True, title="App Name")
>>> print(p.to_dict())
{'name': {'type': ['string'], 'title': 'App Name'}}
"""
type_dict = self.type_dict
if self.title:
type_dict.update({"title": self.title})
if self.nullable or self.optional:
type_dict = append_type(type_dict, "null")
if self.default is not None:
Expand Down
Loading
Loading