-
Notifications
You must be signed in to change notification settings - Fork 4.2k
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
[low-code cdk] Allow for spec file to be defined in the yaml manifest instead of an external file #18411
Conversation
…d rework the code for better testing
@@ -243,7 +243,7 @@ def is_object_definition_with_class_name(definition): | |||
|
|||
@staticmethod | |||
def is_object_definition_with_type(definition): | |||
return isinstance(definition, dict) and "type" in definition | |||
return isinstance(definition, dict) and "type" in definition and definition["type"] != "object" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this makes me slightly nervous, but the connection_specification
is effectively just a mapping/dict/object so when we are generating the spec component, it gets read in as an object and type
: object
gets added the component. Then, when because it has a type
we attempt to resolve it in the CLASS_TYPES_REGISTRY
which will correctly fail because type
isn't in that map.
I'm not sure why we never saw this before, but I spot checked some components and didn't see any fields that were of mapping type so that might be why.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
interesting. I assume the request options provider's field work because they're RequestInput
, not raw Mapping
?
|
||
spec: | ||
documentation_url: https://docsurl.com | ||
connection_specification: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's worth noting that this went from camel case to snake case. I like better consistency with our component definitions across the file and the response of a spec
call will still be camel case to retain compatibility which I mention in another comment.
However, it does make it a small gotcha for migrating existing connectors that it needs to be switched, but it will be very clear and obvious since the YAML will fail validation and report that the fields are missing. I not married to either format tho.
spec: | ||
documentation_url: https://docsurl.com | ||
connection_specification: | ||
$schema: http://json-schema.org/draft-07/schema# |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
left it here as a reminder, but we can actually remove the $schema
from the generator because it will get inferred to the latest version when its not present. I kind of like removing it because right now it exposes an underlying implementation detail of how we validate schemas.
Con is that it becomes a small gotcha for a developer that there could be a mismatch in schema drafts if there is some kind of incompatibility between draft versions when we always use the latest
@@ -69,6 +70,27 @@ def streams(self, config: Mapping[str, Any]) -> List[Stream]: | |||
self._apply_log_level_to_stream_logger(self.logger, stream) | |||
return source_streams | |||
|
|||
def spec(self, logger: logging.Logger) -> ConnectorSpecification: | |||
""" | |||
Returns the spec for this integration. The spec is a JSON-Schema object describing the required configurations (e.g: username |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Returns the spec for this integration. The spec is a JSON-Schema object describing the required configurations (e.g: username | |
Returns the connector specification (spec) as defined in the Airbyte Protocol. The spec is an object describing the possible configurations (e.g: username |
def spec(self, logger: logging.Logger) -> ConnectorSpecification: | ||
""" | ||
Returns the spec for this integration. The spec is a JSON-Schema object describing the required configurations (e.g: username | ||
and password) required to run this integration. For low-code connectors, this will first attempt to load the spec from the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
and password) required to run this integration. For low-code connectors, this will first attempt to load the spec from the | |
and password) which can be configured when running this connector. For low-code connectors, this will first attempt to load the spec from the |
@@ -243,7 +243,7 @@ def is_object_definition_with_class_name(definition): | |||
|
|||
@staticmethod | |||
def is_object_definition_with_type(definition): | |||
return isinstance(definition, dict) and "type" in definition | |||
return isinstance(definition, dict) and "type" in definition and definition["type"] != "object" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the name of the method is_object...
should this check be !=
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The problem with how are connector specifications are defined, the type
field is an overloaded term. In the manifest, type
is just shorthand so we don't have to include the full classpath. But for the connector specification object, type: object
is actually part of the spec schema. So the factory gets confused and thinks we need to parse the spec as another declarative component, but we really rant this to remain a Mapping. Without this line, it looks for object
in the CLASS_TYPES_REGISTRY
It's a little bit of a hack, but it allows us to make the manifest defined and external defined specs interchangable
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@brianjlai should we rename our type
field to something less overloaded as part of lowcode to beta?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you add this context as a comment?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yup will add!
|
||
Attributes: | ||
documentation_url (str): The link the Airbyte documentation about this connector | ||
connection_specification (str): information related to how a connector can be configured |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the type should be Mapping[str, Any]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oops yeah, commented to quickly
options: InitVar[Mapping[str, Any]] | ||
|
||
def __post_init__(self, options: Mapping[str, Any]): | ||
self._options = options |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should we explicitly not store the options to ensure they are not used as part of the spec?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah that's a good point if we want to treat spec exactly how it is today. will update this to not persist or use options anywhere
spec = self._source_config.get("spec") | ||
if spec: | ||
if "class_name" not in spec: | ||
spec["class_name"] = "airbyte_cdk.sources.declarative.spec.Spec" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what if the developer uses a type
field instead of class_name
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All three formats should work. If they define the class_name
then we'll just use that. We define Spec
in the class_types_registry
so it should work with type
, and if it's omitted here then we'll insert class name here.
documentation_url = spec.documentation_url | ||
connection_specification = spec.connection_specification | ||
assert documentation_url == "https://airbyte.com/#yaml-from-manifest" | ||
print(connection_specification) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: remove the debug print
…nent * master: 🪟 🎉 Enable frontend validation for <1hr syncs (cloud) #19028 Stream returns AirbyteMessages (#18572) 🎉 New Source - Recruitee [low-code SDK] (#18671) 🎉 New source: Breezometer [low-code cdk] (#18650) Check disabled connections after protocol update (#18990) Simple default replication worker refactor (#19002) 🎉 New Source: Visma e-conomic (#18595) 🎉 New Source: Fastbill (#18593) Bmoric/extract state api (#18980) 🎉 New Source: Zapier Supported Storage (#18442) 🎉 New source: Klarna (#18385) `AirbyteEstimateTraceMessage` (#18875) Extract source definition api (#18977) [low-code cdk] Allow for spec file to be defined in the yaml manifest instead of an external file (#18411) 🐛 Source HubSpot: fix property scopes (#18624) Ensure that only 6-character hex values are passed to monaco editor (#18943)
return ConnectorSpecification.parse_obj( | ||
{"documentationUrl": self.documentation_url, "connectionSpecification": self.connection_specification} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@brianjlai how this will affect existing low-code connectors using the previous format?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It should not, in the event that a spec
is not defined in the manifest itself, it will default to using the original way of retrieving and generating the spec from the spec.yaml
or spec.json
file.
What
Allows for developers to define the connector specification used in the
spec
command within the manifest file instead of a separatespec.json
orspec.yaml
How
spec
component so we can get additional schema validation and make some of our casing more consistent (it still outputs the same format)Recommended reading order
spec.py
y.python
Validation
Once we have this all reviewed I'll migrate either a connector from hacktoberfest or greenhouse to define it in the manifest to verify publishing still works. I think it should since specs in the various mask files are generated from the
spec
command run on the container and not the literalspec.yaml
file itself.