crosscontract 0.2.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. crosscontract/__init__.py +14 -0
  2. crosscontract/contracts/__init__.py +9 -0
  3. crosscontract/contracts/contracts/__init__.py +4 -0
  4. crosscontract/contracts/contracts/base_contract.py +103 -0
  5. crosscontract/contracts/contracts/cross_contract.py +64 -0
  6. crosscontract/contracts/schema/__init__.py +29 -0
  7. crosscontract/contracts/schema/adapters/__init__.py +18 -0
  8. crosscontract/contracts/schema/adapters/abstract_adapter.py +30 -0
  9. crosscontract/contracts/schema/adapters/pandera_adapter.py +557 -0
  10. crosscontract/contracts/schema/adapters/pydantic_adapter.py +288 -0
  11. crosscontract/contracts/schema/adapters/sqlalchemy_adapter.py +168 -0
  12. crosscontract/contracts/schema/adapters/utils.py +30 -0
  13. crosscontract/contracts/schema/exceptions/__init__.py +1 -0
  14. crosscontract/contracts/schema/exceptions/validation_error.py +222 -0
  15. crosscontract/contracts/schema/field_descriptors/__init__.py +20 -0
  16. crosscontract/contracts/schema/field_descriptors/descriptors.py +66 -0
  17. crosscontract/contracts/schema/field_descriptors/field_descriptors.py +89 -0
  18. crosscontract/contracts/schema/fields/__init__.py +18 -0
  19. crosscontract/contracts/schema/fields/base.py +61 -0
  20. crosscontract/contracts/schema/fields/datetime_field.py +37 -0
  21. crosscontract/contracts/schema/fields/list_field.py +55 -0
  22. crosscontract/contracts/schema/fields/numeric_field.py +44 -0
  23. crosscontract/contracts/schema/fields/string_field.py +48 -0
  24. crosscontract/contracts/schema/reference/__init__.py +4 -0
  25. crosscontract/contracts/schema/reference/foreign_key.py +150 -0
  26. crosscontract/contracts/schema/reference/primary_key.py +52 -0
  27. crosscontract/contracts/schema/schema.py +253 -0
  28. crosscontract/contracts/schema/validation/__init__.py +3 -0
  29. crosscontract/contracts/schema/validation/validate_dataframe.py +81 -0
  30. crosscontract/contracts/utils.py +35 -0
  31. crosscontract/contracts/valid_items.py +14 -0
  32. crosscontract/crossclient/__init__.py +3 -0
  33. crosscontract/crossclient/crossclient.py +146 -0
  34. crosscontract/crossclient/exceptions/__init__.py +24 -0
  35. crosscontract/crossclient/exceptions/exception_factory.py +104 -0
  36. crosscontract/crossclient/exceptions/exceptions.py +104 -0
  37. crosscontract/crossclient/logger.py +4 -0
  38. crosscontract/crossclient/services/__init__.py +4 -0
  39. crosscontract/crossclient/services/contract_resource.py +303 -0
  40. crosscontract/crossclient/services/contract_service.py +267 -0
  41. crosscontract/py.typed +0 -0
  42. crosscontract-0.2.2.dist-info/METADATA +23 -0
  43. crosscontract-0.2.2.dist-info/RECORD +45 -0
  44. crosscontract-0.2.2.dist-info/WHEEL +4 -0
  45. crosscontract-0.2.2.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,14 @@
1
+ """crosscontract package for data contracts and related utilities."""
2
+
3
+ from .contracts import BaseContract, CrossContract, SchemaValidationError, TableSchema
4
+ from .crossclient import CrossClient
5
+
6
+ __version__ = "0.2.2"
7
+
8
+ __all__ = [
9
+ "CrossClient",
10
+ "CrossContract",
11
+ "TableSchema",
12
+ "BaseContract",
13
+ "SchemaValidationError",
14
+ ]
@@ -0,0 +1,9 @@
1
+ from .contracts import BaseContract, CrossContract
2
+ from .schema import SchemaValidationError, TableSchema
3
+
4
+ __all__ = [
5
+ "TableSchema",
6
+ "BaseContract",
7
+ "CrossContract",
8
+ "SchemaValidationError",
9
+ ]
@@ -0,0 +1,4 @@
1
+ from .base_contract import BaseContract, BaseMetaData
2
+ from .cross_contract import CrossContract, CrossMetaData
3
+
4
+ __all__ = ["BaseContract", "CrossContract", "BaseMetaData", "CrossMetaData"]
@@ -0,0 +1,103 @@
1
+ from pathlib import Path
2
+ from typing import Self
3
+
4
+ from pydantic import BaseModel, ConfigDict, Field, model_validator
5
+
6
+ from ..schema import TableSchema
7
+ from ..utils import read_yaml_or_json_file
8
+
9
+
10
+ class BaseMetaData(BaseModel):
11
+ """
12
+ The BaseMetadata class encapsulates the essential metadata attributes
13
+ required for defining a data contract. Every data contract MUST include
14
+ these metadata fields to ensure proper identification and description.
15
+ To extend the metadata for specific use cases, inherit from this class
16
+ and add additional fields as necessary. Then use the extended metadata
17
+ class as a base for your custom contract together with BaseContract.
18
+
19
+ Attributes:
20
+ name (str): A unique identifier for the data contract.
21
+ Must contain only alphanumeric characters, underscores, or hyphens.
22
+ Maximum length is 100 characters.
23
+ """
24
+
25
+ model_config = ConfigDict(extra="forbid")
26
+
27
+ name: str = Field(
28
+ pattern="^[a-zA-Z0-9_-]+$",
29
+ max_length=100,
30
+ description="A unique identifier for the data contract.",
31
+ )
32
+
33
+
34
+ class BaseContract(BaseMetaData):
35
+ """
36
+ The BaseContract class is the most basic representation of a data contract.
37
+ It combines the minimum required metadata with the contract structure given by
38
+ Schema.
39
+
40
+ It serves as the foundational blueprint for defining data contracts.
41
+ Any custom contract implementation MUST inherit from this class to ensure
42
+ structural consistency and compatibility with the system.
43
+
44
+ Attributes:
45
+ name (str): A unique identifier for the data contract.
46
+ Must contain only alphanumeric characters, underscores, or hyphens.
47
+ Maximum length is 100 characters.
48
+ tableschema (TableSchema): The schema defining the structure of the contract
49
+ (fields, primary keys, foreign keys, field descriptors).
50
+
51
+ Example:
52
+ To implement a custom contract with additional metadata:
53
+
54
+ ```python
55
+ from pydantic import Field
56
+ from crosscontract.contracts import BaseContract
57
+
58
+ class MyCustomContract(BaseContract):
59
+ # Add custom metadata fields
60
+ owner: str = Field(description="The owner of this dataset")
61
+ version: str = Field(description="Semantic version of the contract")
62
+
63
+ # The 'schema' field is already inherited from BaseContract!
64
+ ```
65
+ """
66
+
67
+ model_config = ConfigDict(populate_by_name=True, extra="forbid")
68
+
69
+ tableschema: TableSchema = Field(
70
+ description="The Frictionless Table Schema definition.",
71
+ )
72
+
73
+ @classmethod
74
+ def from_file(cls, file_path: str | Path) -> Self:
75
+ """
76
+ Load a BaseContract from a YAML or JSON file.
77
+
78
+ Args:
79
+ file_path (str | Path): The path to the YAML or JSON file.
80
+
81
+ Returns:
82
+ Self: An instance of BaseContract loaded from the file.
83
+
84
+ Raises:
85
+ FileNotFoundError: If the specified file does not exist.
86
+ ValueError: If the file format is not supported (not .json, .yaml, or .yml).
87
+ """
88
+ data = read_yaml_or_json_file(file_path)
89
+ return cls.model_validate(data)
90
+
91
+ @model_validator(mode="after")
92
+ def validate_self_reference(self) -> Self:
93
+ """Validate that self-referencing foreign keys are given as None on the
94
+ resource field. Raise if a reference has the same name as the contract itself.
95
+ """
96
+ for fk in self.tableschema.foreignKeys:
97
+ if fk.reference.resource == self.name:
98
+ raise ValueError(
99
+ f"Foreign key reference resource '{fk.reference.resource}' "
100
+ "cannot be the same as the contract name. Self-references must "
101
+ "use None for the resource field."
102
+ )
103
+ return self
@@ -0,0 +1,64 @@
1
+ from pydantic import ConfigDict, Field
2
+
3
+ from .base_contract import BaseContract, BaseMetaData
4
+
5
+
6
+ class CrossMetaData(BaseMetaData):
7
+ """
8
+ Metadata specific to the CrossContract system,
9
+ extending the base metadata requirements
10
+
11
+ Attributes:
12
+ title (str): A human-readable title for the data.
13
+ description (str): A human-readable description of the data.
14
+ tags (list[str] | None): A list of tags for categorization and filtering.
15
+ """
16
+
17
+ model_config = ConfigDict(str_strip_whitespace=True)
18
+ title: str = Field(
19
+ description=(
20
+ "A human-readable title for the data."
21
+ "Think of this as the label that will be used in graphs and tables."
22
+ ),
23
+ )
24
+
25
+ description: str = Field(
26
+ description=(
27
+ "A human-readable description of the data. This should explain what "
28
+ " the data is about."
29
+ )
30
+ )
31
+
32
+ tags: list[str] = Field(
33
+ default_factory=list,
34
+ description=(
35
+ "A list of tags that can be used to categorize the table. "
36
+ "This can be used to filter tables in the UI."
37
+ ),
38
+ )
39
+
40
+
41
+ class CrossContract(BaseContract, CrossMetaData):
42
+ """
43
+ A concrete implementation of a data contract for the CrossContract system.
44
+
45
+ This class extends `BaseContract` by adding tagging capabilities.
46
+ It serves as the standard contract definition for resources within the
47
+ CrossContract ecosystem.
48
+
49
+ Attributes:
50
+ name (str): A unique identifier for the data contract.
51
+ Must contain only alphanumeric characters, underscores, or hyphens.
52
+ Inherited from BaseContract.
53
+ title (str): A human-readable title for the data.
54
+ description (str): A human-readable description of the data.
55
+ tags (list[str] | None): A list of tags used for categorization and filtering.
56
+ schema (Schema): The Frictionless Table Schema definition.
57
+ Accessible via the `schema` property as well.
58
+ """
59
+
60
+ model_config = ConfigDict(
61
+ populate_by_name=True,
62
+ extra="forbid",
63
+ serialize_by_alias=True,
64
+ )
@@ -0,0 +1,29 @@
1
+ """The schema is at the core of a contract, defining the structure and types of
2
+ data it holds. Schemas ensure data integrity and consistency across different systems
3
+ and applications. Schemas are based on Fritionless Schema
4
+ standard (https://frictionlessdata.io/docs/specs/schema/).
5
+
6
+ A schema consists of a collection of fields, each representing a specific data
7
+ type and its associated constraints. The schema defines how data should be
8
+ validated in the context of a contract.
9
+
10
+ To make schemas operational, there are methods to convert schema definitions into
11
+ Pydantic or Pandera models for data validation and manipulation, as well as into
12
+ SQLAlchemy columns, enabling seamless integration with databases.
13
+ """
14
+
15
+ from .adapters import (
16
+ convert_schema_to_pandera,
17
+ convert_schema_to_pydantic,
18
+ convert_schema_to_sqlalchemy,
19
+ )
20
+ from .exceptions import SchemaValidationError
21
+ from .schema import TableSchema
22
+
23
+ __all__ = [
24
+ "TableSchema",
25
+ "convert_schema_to_pydantic",
26
+ "convert_schema_to_pandera",
27
+ "convert_schema_to_sqlalchemy",
28
+ "SchemaValidationError",
29
+ ]
@@ -0,0 +1,18 @@
1
+ """Adapters take a schema and convert it to a different format.
2
+ For example, the PydanticAdapter converts a schema into a corresponding pydantic
3
+ model that allows to validate a single row of data against the schema. Likewise,
4
+ the PanderaAdapter converts a schema into a corresponding pandera schema that allows
5
+ to validate a dataframe against the schema."""
6
+
7
+ from .pandera_adapter import PanderaPandasAdapter, convert_schema_to_pandera
8
+ from .pydantic_adapter import PydanticAdapter, convert_schema_to_pydantic
9
+ from .sqlalchemy_adapter import SQLAlchemyPostgresAdapter, convert_schema_to_sqlalchemy
10
+
11
+ __all__ = [
12
+ "PydanticAdapter",
13
+ "convert_schema_to_pydantic",
14
+ "SQLAlchemyPostgresAdapter",
15
+ "convert_schema_to_sqlalchemy",
16
+ "PanderaPandasAdapter",
17
+ "convert_schema_to_pandera",
18
+ ]
@@ -0,0 +1,30 @@
1
+ from abc import ABC, abstractmethod
2
+ from typing import TYPE_CHECKING, Any
3
+
4
+ if TYPE_CHECKING: # pragma: no cover
5
+ from crosscontract.contracts.schema import TableSchema
6
+
7
+
8
+ class AbstractAdapter(ABC):
9
+ """
10
+ Abstract base class for schema adapters. Adapters are responsible for converting
11
+ a TableSchema into a specific format, such as a Pydantic model, a Pandera DataFrame,
12
+ or SQLAlchemy columns.
13
+ """
14
+
15
+ def __init__(self, schema: "TableSchema", *args, **kwargs: Any):
16
+ self.schema = schema
17
+
18
+ @abstractmethod
19
+ def convert(self, *args, **kwargs) -> Any:
20
+ """Convert the given TableSchema into the target format."""
21
+ pass
22
+
23
+ @classmethod
24
+ @abstractmethod
25
+ def convert_schema(cls, schema: "TableSchema", *args, **kwargs) -> Any:
26
+ """Convenience method to convert a TableSchema without needing to instantiate
27
+ the adapter. Needs to be implemented by each adapter class to force
28
+ documentation of the expected arguments. But can simply call the instance
29
+ method by default."""
30
+ return cls(schema).convert(*args, **kwargs)