From 8268b7bf80780eb58339fb43b396b848e87dfe31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?JB=20Onofr=C3=A9?= Date: Mon, 6 Jul 2026 08:57:07 +0200 Subject: [PATCH 01/13] Welcome Apache Ossie (incubating) --- .asf.yaml | 73 +++++ CONTRIBUTING.md | 279 +++++++++++++++--- DISCLAIMER | 17 ++ LICENSE | 27 ++ NOTICE | 7 +- README.md | 41 ++- ROADMAP.md | 227 +++++++------- converters/dbt/README.md | 73 +++-- converters/dbt/pyproject.toml | 33 ++- converters/dbt/src/osi_dbt/__init__.py | 11 - converters/dbt/src/ossie_dbt/__init__.py | 28 ++ .../dbt/src/{osi_dbt => ossie_dbt}/cli.py | 53 ++-- .../converter_issues.py | 17 ++ .../expression_utils.py | 17 ++ .../{osi_dbt => ossie_dbt}/filter_utils.py | 21 +- .../src/{osi_dbt => ossie_dbt}/msi_to_osi.py | 33 ++- .../src/{osi_dbt => ossie_dbt}/osi_to_msi.py | 35 ++- converters/dbt/tests/__init__.py | 17 ++ converters/dbt/tests/helpers.py | 25 +- converters/dbt/tests/test_msi_to_osi.py | 27 +- converters/dbt/tests/test_osi_to_msi.py | 23 +- converters/gooddata/README.md | 41 ++- converters/gooddata/pyproject.toml | 32 +- .../gooddata/src/gooddata_osi/__init__.py | 6 - .../gooddata/src/ossie_gooddata/__init__.py | 23 ++ .../gooddata_to_osi.py | 45 ++- .../models.py | 21 +- .../osi_to_gooddata.py | 45 ++- converters/gooddata/tests/__init__.py | 17 ++ converters/gooddata/tests/conftest.py | 23 +- .../gooddata/tests/fixtures/osi_tpcds.yaml | 17 ++ .../gooddata/tests/test_gooddata_to_osi.py | 33 ++- converters/gooddata/tests/test_models.py | 19 +- .../gooddata/tests/test_osi_to_gooddata.py | 31 +- converters/gooddata/tests/test_roundtrip.py | 51 ++-- converters/gooddata/uv.lock | 246 +++++++-------- converters/index.md | 85 +++--- converters/polaris/README.md | 55 ++-- converters/polaris/pom.xml | 58 +++- .../converter/polaris/OsiModelParser.java | 31 +- .../polaris/OsiPolarisConverter.java | 41 ++- .../converter/polaris/OsiYamlGenerator.java | 31 +- .../converter/polaris/PolarisClient.java | 21 +- .../converter/polaris/PolarisExporter.java | 41 ++- .../converter/polaris/PolarisImporter.java | 37 ++- .../converter/polaris/model/OsiModel.java | 23 +- .../polaris/OsiPolarisConverterTest.java | 25 +- converters/salesforce/README.md | 79 +++-- converters/salesforce/pom.xml | 60 +++- .../ossie}/app/OsiSalesforceConverter.java | 35 ++- .../ossie}/converter/AbstractConverter.java | 29 +- .../ossie/converter/ConversionDirection.java | 49 +++ .../org/apache/ossie/converter/Converter.java | 55 ++++ .../ossie}/converter/ConverterConstants.java | 25 +- .../ossie/converter/ConverterFactory.java | 37 +++ .../ossie}/converter/ConverterImpl.java | 39 ++- .../converter/CustomExtensionHandler.java | 55 ++-- .../converter/DatasetMappingHandler.java | 45 ++- .../ossie}/converter/FieldMappingHandler.java | 79 +++-- .../converter/GenericMappingEngine.java | 27 +- .../converter/MetricMappingHandler.java | 49 ++- .../converter/RelationshipMappingHandler.java | 47 ++- .../SemanticModelMappingHandler.java | 55 ++-- .../converter/pipeline/DirectionConfig.java | 23 +- .../converter/pipeline/HandlerFactory.java | 25 +- .../converter/pipeline/PipelineConfig.java | 48 +++ .../pipeline/PipelineConfigLoader.java | 23 +- .../converter/pipeline/PipelineStep.java | 38 +++ .../ossie/exception/ConversionException.java | 47 +++ .../exception/InvalidInputException.java | 48 +++ .../ossie/exception/ValidationException.java | 35 +++ .../mapper/FileBasedPropertyMapper.java | 31 +- .../apache/ossie/mapper/PropertyMapper.java | 49 +++ .../ossie}/util/DataStructureUtils.java | 21 +- .../ossie}/util/MappingUtils.java | 21 +- .../{osi => apache/ossie}/util/PathUtils.java | 25 +- .../ossie}/validator/SchemaValidator.java | 27 +- .../osi/converter/ConversionDirection.java | 30 -- .../java/org/osi/converter/Converter.java | 36 --- .../org/osi/converter/ConverterFactory.java | 18 -- .../converter/pipeline/PipelineConfig.java | 29 -- .../osi/converter/pipeline/PipelineStep.java | 19 -- .../osi/exception/ConversionException.java | 28 -- .../osi/exception/InvalidInputException.java | 29 -- .../osi/exception/ValidationException.java | 16 - .../java/org/osi/mapper/PropertyMapper.java | 30 -- .../src/main/resources/mappings.yaml | 21 +- .../osi-salesforce-converter-config.yaml | 17 ++ .../ossie}/OsiToSalesforceConverterTest.java | 49 ++- .../ossie}/SalesforceToOsiConverterTest.java | 47 ++- .../resources/examples/osiToSalesforce.yaml | 17 ++ converters/snowflake/README.md | 25 +- converters/snowflake/requirements.txt | 17 ++ .../src/osi_to_snowflake_yaml_converter.py | 79 +++-- ...xample_converted_tpcds_semantic_model.yaml | 17 ++ .../test_osi_to_snowflake_yaml_converter.py | 27 +- core-spec/osi-schema.json | 8 +- core-spec/spec.md | 21 +- core-spec/spec.yaml | 19 +- docs/index.md | 177 ++++++----- docs/working_groups.md | 21 +- examples/flights.yaml | 17 ++ examples/tpcds_semantic_model.yaml | 18 +- ontology/ontology.json | 10 +- ontology/ontology.md | 23 +- python/pyproject.toml | 27 +- python/src/osi/__init__.py | 33 --- python/src/ossie/__init__.py | 50 ++++ python/src/{osi => ossie}/models.py | 23 +- validation/validate.py | 25 +- 110 files changed, 3217 insertions(+), 1214 deletions(-) create mode 100644 .asf.yaml create mode 100644 DISCLAIMER delete mode 100644 converters/dbt/src/osi_dbt/__init__.py create mode 100644 converters/dbt/src/ossie_dbt/__init__.py rename converters/dbt/src/{osi_dbt => ossie_dbt}/cli.py (58%) rename converters/dbt/src/{osi_dbt => ossie_dbt}/converter_issues.py (52%) rename converters/dbt/src/{osi_dbt => ossie_dbt}/expression_utils.py (85%) rename converters/dbt/src/{osi_dbt => ossie_dbt}/filter_utils.py (83%) rename converters/dbt/src/{osi_dbt => ossie_dbt}/msi_to_osi.py (93%) rename converters/dbt/src/{osi_dbt => ossie_dbt}/osi_to_msi.py (92%) delete mode 100644 converters/gooddata/src/gooddata_osi/__init__.py create mode 100644 converters/gooddata/src/ossie_gooddata/__init__.py rename converters/gooddata/src/{gooddata_osi => ossie_gooddata}/gooddata_to_osi.py (81%) rename converters/gooddata/src/{gooddata_osi => ossie_gooddata}/models.py (93%) rename converters/gooddata/src/{gooddata_osi => ossie_gooddata}/osi_to_gooddata.py (86%) rename converters/polaris/src/main/java/org/{osi => apache/ossie}/converter/polaris/OsiModelParser.java (85%) rename converters/polaris/src/main/java/org/{osi => apache/ossie}/converter/polaris/OsiPolarisConverter.java (73%) rename converters/polaris/src/main/java/org/{osi => apache/ossie}/converter/polaris/OsiYamlGenerator.java (84%) rename converters/polaris/src/main/java/org/{osi => apache/ossie}/converter/polaris/PolarisClient.java (90%) rename converters/polaris/src/main/java/org/{osi => apache/ossie}/converter/polaris/PolarisExporter.java (80%) rename converters/polaris/src/main/java/org/{osi => apache/ossie}/converter/polaris/PolarisImporter.java (86%) rename converters/polaris/src/main/java/org/{osi => apache/ossie}/converter/polaris/model/OsiModel.java (87%) rename converters/polaris/src/test/java/org/{osi => apache/ossie}/converter/polaris/OsiPolarisConverterTest.java (94%) rename converters/salesforce/src/main/java/org/{osi => apache/ossie}/app/OsiSalesforceConverter.java (61%) rename converters/salesforce/src/main/java/org/{osi => apache/ossie}/converter/AbstractConverter.java (86%) create mode 100644 converters/salesforce/src/main/java/org/apache/ossie/converter/ConversionDirection.java create mode 100644 converters/salesforce/src/main/java/org/apache/ossie/converter/Converter.java rename converters/salesforce/src/main/java/org/{osi => apache/ossie}/converter/ConverterConstants.java (84%) create mode 100644 converters/salesforce/src/main/java/org/apache/ossie/converter/ConverterFactory.java rename converters/salesforce/src/main/java/org/{osi => apache/ossie}/converter/ConverterImpl.java (80%) rename converters/salesforce/src/main/java/org/{osi => apache/ossie}/converter/CustomExtensionHandler.java (86%) rename converters/salesforce/src/main/java/org/{osi => apache/ossie}/converter/DatasetMappingHandler.java (74%) rename converters/salesforce/src/main/java/org/{osi => apache/ossie}/converter/FieldMappingHandler.java (90%) rename converters/salesforce/src/main/java/org/{osi => apache/ossie}/converter/GenericMappingEngine.java (82%) rename converters/salesforce/src/main/java/org/{osi => apache/ossie}/converter/MetricMappingHandler.java (70%) rename converters/salesforce/src/main/java/org/{osi => apache/ossie}/converter/RelationshipMappingHandler.java (90%) rename converters/salesforce/src/main/java/org/{osi => apache/ossie}/converter/SemanticModelMappingHandler.java (68%) rename converters/salesforce/src/main/java/org/{osi => apache/ossie}/converter/pipeline/DirectionConfig.java (58%) rename converters/salesforce/src/main/java/org/{osi => apache/ossie}/converter/pipeline/HandlerFactory.java (62%) create mode 100644 converters/salesforce/src/main/java/org/apache/ossie/converter/pipeline/PipelineConfig.java rename converters/salesforce/src/main/java/org/{osi => apache/ossie}/converter/pipeline/PipelineConfigLoader.java (71%) create mode 100644 converters/salesforce/src/main/java/org/apache/ossie/converter/pipeline/PipelineStep.java create mode 100644 converters/salesforce/src/main/java/org/apache/ossie/exception/ConversionException.java create mode 100644 converters/salesforce/src/main/java/org/apache/ossie/exception/InvalidInputException.java create mode 100644 converters/salesforce/src/main/java/org/apache/ossie/exception/ValidationException.java rename converters/salesforce/src/main/java/org/{osi => apache/ossie}/mapper/FileBasedPropertyMapper.java (73%) create mode 100644 converters/salesforce/src/main/java/org/apache/ossie/mapper/PropertyMapper.java rename converters/salesforce/src/main/java/org/{osi => apache/ossie}/util/DataStructureUtils.java (81%) rename converters/salesforce/src/main/java/org/{osi => apache/ossie}/util/MappingUtils.java (81%) rename converters/salesforce/src/main/java/org/{osi => apache/ossie}/util/PathUtils.java (77%) rename converters/salesforce/src/main/java/org/{osi => apache/ossie}/validator/SchemaValidator.java (77%) delete mode 100644 converters/salesforce/src/main/java/org/osi/converter/ConversionDirection.java delete mode 100644 converters/salesforce/src/main/java/org/osi/converter/Converter.java delete mode 100644 converters/salesforce/src/main/java/org/osi/converter/ConverterFactory.java delete mode 100644 converters/salesforce/src/main/java/org/osi/converter/pipeline/PipelineConfig.java delete mode 100644 converters/salesforce/src/main/java/org/osi/converter/pipeline/PipelineStep.java delete mode 100644 converters/salesforce/src/main/java/org/osi/exception/ConversionException.java delete mode 100644 converters/salesforce/src/main/java/org/osi/exception/InvalidInputException.java delete mode 100644 converters/salesforce/src/main/java/org/osi/exception/ValidationException.java delete mode 100644 converters/salesforce/src/main/java/org/osi/mapper/PropertyMapper.java rename converters/salesforce/src/test/java/org/{osi => apache/ossie}/OsiToSalesforceConverterTest.java (89%) rename converters/salesforce/src/test/java/org/{osi => apache/ossie}/SalesforceToOsiConverterTest.java (90%) delete mode 100644 python/src/osi/__init__.py create mode 100644 python/src/ossie/__init__.py rename python/src/{osi => ossie}/models.py (81%) diff --git a/.asf.yaml b/.asf.yaml new file mode 100644 index 00000000..354410e6 --- /dev/null +++ b/.asf.yaml @@ -0,0 +1,73 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# The format of this file is documented at +# https://github.com/apache/infrastructure-asfyaml +# also at +# https://cwiki.apache.org/confluence/display/INFRA/Git+-+.asf.yaml+features + +github: + description: "Apache Ossie, industry wide specification effort to standardize how we exchange semantic metadata across analytics, AI and BI platforms, providing a vendor neutral, single source of truth for semantic data" + homepage: https://ossie.apache.org/ + labels: + - semantic + - metadata + + enabled_merge_buttons: + merge: false + squash: true + rebase: false + + del_branch_on_merge: true + + copilot_code_review: + enabled: true + review_drafts: false + review_on_push: false + + protected_branches: + main: + required_pull_request_reviews: + require_code_owner_reviews: false + dismiss_stale_reviews: true + require_last_push_approval: false + required_approving_review_count: 1 + + required_linear_history: true + + features: + wiki: false + issues: true + projects: true + discussions: true + + ghp_branch: gh-pages + ghp_path: / + + collaborators: + +notifications: + commits: commits@ossie.apache.org + issues: issues@ossie.apache.org + pullrequests: issues@ossie.apache.org + discussions: dev@ossie.apache.org + jira_options: link label + +publish: + whoami: asf-site diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d9fd4a81..9307ff37 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,101 +1,282 @@ -# Contributing to Open Semantic Interchange (OSI) - -We welcome contributions from everyone — whether you are a developer, a data engineer, a BI analyst, or simply someone interested in the future of semantic interoperability. + + +# Contributing to Apache Ossie (incubating) + +Thank you for your interest in contributing to Apache Ossie! We welcome +contributions from everyone — whether you are a developer, a data engineer, a BI +analyst, or simply someone interested in the future of semantic interoperability. + +Apache Ossie was formerly known as **Open Semantic Interchange (OSI)**. + +> **Apache Ossie is an effort undergoing incubation at the Apache Software +> Foundation (ASF), sponsored by the Apache Incubator.** Incubation is required +> of all newly accepted projects until a further review indicates that the +> infrastructure, communications, and decision making process have stabilized in +> a manner consistent with other successful ASF projects. See the +> [DISCLAIMER](DISCLAIMER) for details. + +Apache Ossie is governed by [The Apache Way](https://www.apache.org/theapacheway/), +the ASF's collection of principles and practices for building open, vendor-neutral +communities. If you are new to the ASF, the +[Apache Incubator](https://incubator.apache.org/) and the +[ASF New Committers guide](https://www.apache.org/dev/new-committers-guide.html) +are good starting points. ## Ways to Contribute -- **Specification Feedback**: Review proposed specification changes and share your perspective on GitHub pull requests and issues. +- **Specification Feedback**: Review proposed specification changes and share your perspective on the mailing list, GitHub pull requests, and issues. - **Use Case Discussions**: Share how your organization uses semantic models and what challenges you face — this helps shape the specification to address real-world needs. - **Code Contributions**: Contribute to validation tooling, converters, examples, or any other part of the project. - **Documentation**: Help improve and expand the project documentation. - **Community Support**: Answer questions, participate in discussions, and help onboard new contributors. +## Communication + +At the ASF, **the mailing lists are the primary channel** for the project. Consensus +is built and decisions are recorded on the lists, so please bring discussions there. +A guiding principle of the Apache Way is: *if it didn't happen on the mailing list, +it didn't happen.* + +- **dev@ossie.apache.org** — development and community discussion. Subscribe by + emailing `dev-subscribe@ossie.apache.org`, then browse the + [archives](https://lists.apache.org/list.html?dev@ossie.apache.org). +- **commits@ossie.apache.org** — automated notifications for commits, pull requests. + Subscribe via `commits-subscribe@ossie.apache.org`. +- **issues@ossie.apache.org** — automated notifications for issues. + Subscribe via `issues-subscribe@ossie.apache.org`. +- **private@ossie.apache.org** — the Podling Project Management Committee (PPMC) + private list, used only for confidential matters such as committer nominations. + +Secondary, less formal channels (never a substitute for the lists when a decision +is being made): + +- [GitHub Issues](https://github.com/apache/ossie/issues) and + [Discussions](https://github.com/apache/ossie/discussions) +- [Slack](https://join.slack.com/t/apache-ossie/shared_invite/zt-42zw4rflt-Gpve8_NFJq7AsdAQTY~SCg) + ## Getting Started -1. **Read the Specification**: Familiarize yourself with the [core specification](core-spec/spec.md) to understand the semantic model format. -2. **Explore the Examples**: Review the [TPC-DS example](examples/tpcds_semantic_model.yaml) to see a complete semantic model in practice. -3. **Join the Conversation**: Open or participate in [GitHub Issues](https://github.com/open-semantic-interchange) and Discussions to share ideas and feedback. -4. **Submit a Pull Request**: Fork the repository, make your changes, and submit a pull request. All contributions go through the standard review process described below. -5. **Join the Slack workspace**: Chat directly with the community by [joining Slack](https://join.slack.com/t/opensemanticx/shared_invite/zt-3yuad6c0h-MaoPgVSD1g9MEOf1_QeaiQ). +1. **Subscribe to the dev list**: Email `dev-subscribe@ossie.apache.org` and introduce yourself. +2. **Read the Specification**: Familiarize yourself with the [core specification](core-spec/spec.md) to understand the semantic model format. +3. **Explore the Examples**: Review the [TPC-DS example](examples/tpcds_semantic_model.yaml) to see a complete semantic model in practice. +4. **Find something to work on**: Browse the [issues](https://github.com/apache/ossie/issues), or raise a topic on the dev list. +5. **Submit a Pull Request**: Fork the repository, make your changes, and submit a pull request. All contributions go through the review process described below. -## Contribution Guidelines +## Contributor License Agreement (ICLA) -### Specification Changes +All contributions to Apache Ossie are made under the [Apache License 2.0](LICENSE). +By submitting a pull request or patch, you agree that your contribution is licensed +under those terms (see Section 5 of the license). -Changes to the OSI Specification follow a structured process: +Before your first non-trivial contribution can be merged, and always before you are +granted commit access, you must have an +[Individual Contributor License Agreement (ICLA)](https://www.apache.org/licenses/contributor-agreements.html#clas) +on file with the ASF. If you are contributing on behalf of your employer, a +[Corporate CLA (CCLA)](https://www.apache.org/licenses/contributor-agreements.html#clas) +may also be required. Please keep individual commits signed off and attributed to +the correct author so that provenance is clear. -1. **Proposal**: Submit a GitHub pull request with a clear description of the motivation, the change itself, and its impact on existing implementations. -2. **Discussion period**: The community has a minimum of 7 days to review and discuss the proposed change. Complex changes may require a longer review window. -3. **Vote**: Once the discussion period has elapsed, a vote is called. TSC members and committers cast votes: - - **+1** (Yes): In favor of the change - - **0** (Abstain): No opinion or willing to go with the majority - - **-1** (Veto): Against the change — must be accompanied by a technical justification +## Contribution Workflow - TSC member votes are binding. A specification change passes with at least 2 binding +1 votes and no vetoes. A veto can only be overridden by addressing the stated concern or by a supermajority (two-thirds) vote of the TSC. +The project's canonical repository is hosted at +[github.com/apache/ossie](https://github.com/apache/ossie), mirrored from ASF +infrastructure (GitBox). -### All Other Contributions +### Code, Documentation, and Tooling -Non-specification contributions (bug fixes, tooling, documentation, converters) go through standard GitHub pull request review. Committers review and merge these contributions. +Non-specification contributions (bug fixes, tooling, documentation, converters, +examples) follow standard GitHub pull request review: -## Community Values +1. Open an issue or start a thread on `dev@` for anything non-trivial, so the + approach can be discussed before significant work begins. +2. Fork the repository and create a topic branch for your change. +3. Submit a pull request with a clear description of the motivation and the change. +4. A committer reviews and merges once the change has at least one **+1** from a + committer and no unresolved **-1**. The project follows a review-then-commit (RTC) + model: changes are merged after review rather than committed first. -OSI follows the Apache Way, built on these principles: +### Specification Changes -- **Meritocracy**: Merit is based on contribution and never expires. Any constructive contribution earns merit — code, documentation, testing, community support, and specification reviews all count equally. -- **Peer-based**: Every participant is treated as a peer regardless of employer affiliation or seniority. Decisions are made by the community, not by any single company or individual. -- **Consensus decision making**: We strive for consensus in all decisions. When full consensus cannot be reached, a formal vote may be called. -- **Open Communications**: All technical discussions, design decisions, and specification changes happen in the open — on GitHub issues, pull requests, and discussions. If it didn't happen on a public resource, it didn't happen. -- **Responsible oversight**: The community collectively ensures the specification and tooling remain high quality, secure, and aligned with the project's mission. -- **Independence**: The project operates independently of any single vendor or organization. +Changes to the Apache Ossie specification carry a higher bar and follow a structured +process: + +1. **Proposal**: Announce the proposal on `dev@ossie.apache.org` and open a GitHub + pull request with a clear description of the motivation, the change itself, and + its impact on existing implementations. +2. **Discussion period**: The community has a minimum of 7 days (72 hours for a + formal `[VOTE]`) to review and discuss. Complex changes may require a longer + review window. +3. **Vote**: Once discussion has settled, a `[VOTE]` thread is called on the dev + list. See [Decision Making and Voting](#decision-making-and-voting) below. + +## Decision Making and Voting + +Apache Ossie strives for **lazy consensus** — proceeding when no one objects — and +falls back to a formal vote when consensus cannot be reached. Votes happen on +`dev@ossie.apache.org` so they are publicly recorded. Voters express: + +- **+1**: In favor. +- **0**: Abstain / no strong opinion. +- **-1**: Against. For code and specification changes this is a **veto** and + **must** be accompanied by a technical justification; a valid veto can only be + resolved by addressing the stated concern. + +Conventions: + +- **Code and other changes** pass by lazy consensus: at least one binding **+1** and + no vetoes. +- **Specification changes** require at least three binding **+1** votes and no + vetoes. +- **Committer and PPMC nominations** are held on the private list and pass by lazy + consensus (no **-1** within 72 hours). +- **Procedural votes** (e.g. adopting a policy) pass by simple majority and cannot + be vetoed. + +**Binding votes** on the podling are cast by PPMC members. Everyone is encouraged to +vote; non-binding votes are valued input that informs the outcome. + +### Releases + +As an incubating project, every release is approved in two stages: + +1. A `[VOTE]` on `dev@ossie.apache.org` that passes with at least **three binding + +1 votes** from the PPMC and more +1 than -1 votes. +2. A second `[VOTE]` on `general@incubator.apache.org` that passes with at least + three binding +1 votes from the Incubator PMC (IPMC). + +Releases are source releases distributed through official ASF channels and must +comply with [ASF release policy](https://www.apache.org/legal/release-policy.html). + +## Community Values — The Apache Way + +Apache Ossie follows [The Apache Way](https://www.apache.org/theapacheway/), built +on these principles: + +- **Community over code**: A healthy, welcoming community is the project's most + important asset. +- **Meritocracy**: Merit is based on contribution and never expires. Any constructive + contribution earns merit — code, documentation, testing, community support, and + specification reviews all count. +- **Peer-based**: Every participant is treated as a peer regardless of employer + affiliation or seniority. Decisions are made by the community, not by any single + company or individual. +- **Consensus decision making**: We strive for consensus in all decisions. When full + consensus cannot be reached, a formal vote may be called. +- **Open Communications**: Technical discussions, design decisions, and specification + changes happen in the open on the mailing lists and other public resources. + Decisions are made asynchronously so contributors in every timezone can take part. +- **Responsible oversight**: The community collectively ensures the specification and + tooling remain high quality, secure, and aligned with the project's mission. +- **Vendor neutrality**: The project operates independently of any single vendor or + organization. ## Roles and Responsibilities ### Contributors -Anyone who contributes to the project in any form — code, documentation, bug reports, specification feedback, or community support. Contributors do not have binding vote rights but are encouraged to participate in all discussions. +Anyone who contributes to the project in any form — code, documentation, bug reports, +specification feedback, or community support. Contributors are encouraged to +participate in all discussions and votes; contributor votes are non-binding but valued. -### Specification Reviewers +### Committers -Community members with domain expertise (e.g., in BI, AI, data engineering) who review proposed specification changes for correctness, completeness, and practical applicability. +Contributors who have earned write access to the repository through sustained, +high-quality contributions. Committers review and merge pull requests and have +binding votes on the project's technical decisions. All committers must have an ICLA +on file. -### Committers +### Podling Project Management Committee (PPMC) + +The PPMC is responsible for the overall direction and health of the podling — +technical direction, community growth, and oversight of releases. PPMC members' +votes are binding. During incubation, PPMC members work alongside the project's +mentors and, upon graduation, the PPMC becomes the project's PMC. + +### Mentors -Contributors who have earned write access to the repository through sustained, high-quality contributions. Committers can merge pull requests and have binding votes on non-specification matters. +Experienced ASF members assigned by the Incubator to guide the podling through the +incubation process. Mentors are PPMC members who help the community learn the Apache +Way, and they shepherd release votes to the Incubator PMC. -### Technical Steering Committee (TSC) +### Incubator PMC (IPMC) -The TSC is responsible for the overall technical direction of the OSI Specification. TSC members have binding votes on specification changes. New TSC members are elected by existing TSC members. +The [Apache Incubator](https://incubator.apache.org/) PMC provides oversight for all +podlings, including approving releases and reviewing quarterly podling reports until +the project graduates to a top-level project. ## Becoming a Committer -Committership is earned through sustained contribution to the project. There is no fixed formula — the community recognizes contributors who demonstrate consistent, high-quality involvement over time. +Committership is earned through sustained contribution to the project. There is no +fixed formula — the community recognizes contributors who demonstrate consistent, +high-quality involvement over time. ### What counts -Committer candidacy is based on the breadth and quality of your contributions across any of these areas: +Committer candidacy is based on the breadth and quality of your contributions across +any of these areas: - Code contributions (converters, validation tooling, examples, tests) - Specification feedback and review participation - Documentation improvements -- Community support (answering questions, helping onboard others) +- Community support (answering questions, helping onboard others, discussion on the lists) - Working group participation ### The process -1. Any existing committer or TSC member may nominate a contributor by opening a discussion (GitHub Discussions or the project mailing list). -2. The nomination includes a summary of the candidate's contributions and a rationale for committer status. -3. Existing committers and TSC members discuss the nomination. The standard consensus voting convention applies (+1 / 0 / -1). -4. A nomination passes with lazy consensus — if no -1 votes are received within 72 hours, the candidate is welcomed as a committer. -5. The new committer is added to the repository with write access and listed in the project documentation. - -There is no minimum time requirement. What matters is the quality, consistency, and impact of your contributions. A focused burst of high-value work can carry as much weight as a longer track record. +1. Any PPMC member may nominate a contributor by starting a discussion on the private + list (`private@ossie.apache.org`). +2. The nomination includes a summary of the candidate's contributions and a rationale + for committer status. +3. The PPMC discusses and votes. A nomination passes by lazy consensus — if no **-1** + votes are received within 72 hours. +4. If the vote passes, the candidate is invited to become a committer. Once they + accept and their ICLA is on file, their Apache account is set up and write access + is granted. +5. The new committer is announced to the community and listed in the project + documentation. + +There is no minimum time requirement. What matters is the quality, consistency, and +impact of your contributions. Contributors who show sound judgment and help grow the +community may also be invited to join the PPMC. ## Code of Conduct -All participants in the OSI community are expected to treat each other with respect and professionalism. We are committed to providing a welcoming and inclusive environment for everyone, regardless of background or experience level. +All participants in the Apache Ossie community are expected to follow the +[Apache Software Foundation Code of Conduct](https://www.apache.org/foundation/policies/conduct). +We are committed to providing a welcoming and inclusive environment for everyone, +regardless of background or experience level. Concerns may be raised privately with +the PPMC (`private@ossie.apache.org`) or, for foundation-level matters, with the ASF +per the linked policy. + +## Trademarks + +Apache Ossie, Ossie, Apache, the Apache feather logo, and the Apache Ossie project +logo are trademarks of The Apache Software Foundation. Please review the +[ASF trademark policy](https://www.apache.org/foundation/marks/) before using any of +these marks. ## License -All content in this repository — including code, specification, and documentation — is licensed under the [Apache License 2.0](LICENSE). +All content in this repository — including code, specification, and documentation — +is licensed under the [Apache License 2.0](LICENSE). -By submitting a contribution, you agree that your contribution will be licensed under the same terms. +By submitting a contribution, you agree that your contribution will be licensed under +the same terms, as described in the [Contributor License Agreement](#contributor-license-agreement-icla) +section above. diff --git a/DISCLAIMER b/DISCLAIMER new file mode 100644 index 00000000..a2d9f5d6 --- /dev/null +++ b/DISCLAIMER @@ -0,0 +1,17 @@ +Apache Ossie is an effort undergoing incubation at the Apache Software +Foundation (ASF), sponsored by the Apache Incubator PMC. + +Incubation is required of all newly accepted projects until a further review +indicates that the infrastructure, communications, and decision making process +have stabilized in a manner consistent with other successful ASF projects. + +While incubation status is not necessarily a reflection of the completeness +or stability of the code, it does indicate that the project has yet to be +fully endorsed by the ASF. + +If you are planning to incorporate this work into your product/project, please +be aware that you will need to conduct a thorough licensing review to determine +the overall implications of including this work. For the current status of this +project through the Apache Incubator visit: +https://incubator.apache.org/projects/ossie.html + diff --git a/LICENSE b/LICENSE index 67db8588..d6456956 100644 --- a/LICENSE +++ b/LICENSE @@ -173,3 +173,30 @@ defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/NOTICE b/NOTICE index 2deb0796..e4bc78e2 100644 --- a/NOTICE +++ b/NOTICE @@ -1,2 +1,5 @@ -Open Semantic Interchange -Copyright 2026 - Open Semantic Interchange +Apache Ossie (incubating) +Copyright 2026 The Apache Software Foundation + +This product includes software developed at +The Apache Software Foundation (http://www.apache.org/). diff --git a/README.md b/README.md index 721eefb1..eee15c8a 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,41 @@ -# OSI + + +# Apache Ossie (incubating) + +Apache Ossie is a collaborative, open-source effort dedicated to standardizing and streamlining semantic model exchange and utilization across the diverse array of tools and platforms within the data analytics, AI, and BI ecosystem. Our shared vision is to establish a common, vendor-agnostic semantic model specification, promoting unparalleled interoperability, efficiency, and collaboration among all participants. By providing a single, consistent source of truth, this vendor-agnostic standard ensures that your data's definitions and value remain consistent as they are interchanged between AI agents, BI platforms, and all other tools in your ecosystem, eliminating inconsistencies across your different tools. + +Apache Ossie was formerly known as **Open Semantic Interchange (OSI)**. + +Apache Ossie provides a single JSON- and YAML-based specification that any tool can read and write, addressing the semantic fragmentation common across today's data stack: the same KPI defined differently across tools, teams spending significant effort manually reconciling definitions, and AI agents producing unreliable outputs grounded in inconsistent business logic. ## What's in this repository -- [`core-spec/`](core-spec/) — The OSI core specification (`spec.md`), the machine-readable schema (`spec.yaml`, `osi-schema.json`), and accompanying documentation. -- [`converters/`](converters/) — Reference converters that translate between OSI and other semantic formats (e.g., dbt, GoodData, Polaris, Salesforce). +- [`core-spec/`](core-spec/) — The Ossie core specification (`spec.md`), the machine-readable schema (`spec.yaml`, `osi-schema.json`), and accompanying documentation. +- [`converters/`](converters/) — Reference converters that translate between Ossie and other semantic formats (e.g., dbt, GoodData, Polaris, Salesforce). - [`examples/`](examples/) — Example semantic models, including a complete TPC-DS model. -- [`validation/`](validation/) — Tooling for validating semantic models against the OSI schema. +- [`validation/`](validation/) — Tooling for validating semantic models against the Ossie schema. - [`docs/`](docs/) — Project documentation and overview. ## Get involved - **Contribute:** See [CONTRIBUTING.md](CONTRIBUTING.md) for how to propose specification changes, contribute code, and participate in the community. - **Roadmap:** See [ROADMAP.md](ROADMAP.md) for current working groups, future efforts, and planned enhancements informed by community discussion. -- **Discuss:** Join the conversation on [GitHub Discussions](https://github.com/open-semantic-interchange/OSI/discussions) and [Issues](https://github.com/open-semantic-interchange/OSI/issues). -- **Join the Slack community:** Chat directly with contributors on [Slack](https://join.slack.com/t/opensemanticx/shared_invite/zt-3yuad6c0h-MaoPgVSD1g9MEOf1_QeaiQ). - -# License - -All content in this repository — including code, specification, and documentation — is licensed under the [Apache License 2.0](LICENSE). +- **Discuss:** Join the conversation on [GitHub Discussions](https://github.com/apache/ossie/discussions) and [Issues](https://github.com/apache/ossie/issues). +- **Join the Slack community:** Chat directly with contributors on [Slack](https://join.slack.com/t/apache-ossie/shared_invite/zt-42zw4rflt-Gpve8_NFJq7AsdAQTY~SCg). diff --git a/ROADMAP.md b/ROADMAP.md index 04a8ae94..5ca262c7 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -1,6 +1,25 @@ -# OSI Roadmap (Community-Informed) + + +# Apache Ossie Roadmap (Community-Informed) + +This roadmap synthesizes community discussions and voting signals from the [Ossie GitHub Discussions](https://github.com/apache/ossie/discussions) board. It groups work into three categories: - **Current Efforts / Working Groups** — strategic initiatives with active working groups driving spec evolution now - **Future Efforts** — strategic initiatives planned for future working groups @@ -23,20 +42,20 @@ The current model lacks sufficient support for metrics at different grains, filt **Key Discussions:** -- [Top-level "metrics" vs. dataset-level "measures"](https://github.com/open-semantic-interchange/OSI/discussions/29) -- [Cumulative and other "expansions" to metrics](https://github.com/open-semantic-interchange/OSI/discussions/39) -- [Structured aggregation_method for Metrics](https://github.com/open-semantic-interchange/OSI/discussions/19) -- [Add "entity / grain" as a first-class concept](https://github.com/open-semantic-interchange/OSI/discussions/12) -- [Add explicit datasets reference to Metrics](https://github.com/open-semantic-interchange/OSI/discussions/18) -- [Relationship Semantics](https://github.com/open-semantic-interchange/OSI/discussions/24) -- [Complex Relationship Definitions](https://github.com/open-semantic-interchange/OSI/discussions/4) -- [Make Relationship Cardinality Explicit](https://github.com/open-semantic-interchange/OSI/discussions/50) -- [Inner join in relationships](https://github.com/open-semantic-interchange/OSI/discussions/11) -- [Support for cross-dataset dimensions & single-dataset measures](https://github.com/open-semantic-interchange/OSI/discussions/27) -- [Semantic Filters](https://github.com/open-semantic-interchange/OSI/discussions/5) -- [BIG IDEA: add metrics trees / input-output relations between metrics](https://github.com/open-semantic-interchange/OSI/discussions/40) -- [Primary Key vs Unique Keys redundancy](https://github.com/open-semantic-interchange/OSI/discussions/15) -- [Clarifying the Semantic Intent of Primary Keys vs. Unique Constraints](https://github.com/open-semantic-interchange/OSI/discussions/119) +- [Top-level "metrics" vs. dataset-level "measures"](https://github.com/apache/ossie/discussions/29) +- [Cumulative and other "expansions" to metrics](https://github.com/apache/ossie/discussions/39) +- [Structured aggregation_method for Metrics](https://github.com/apache/ossie/discussions/19) +- [Add "entity / grain" as a first-class concept](https://github.com/apache/ossie/discussions/12) +- [Add explicit datasets reference to Metrics](https://github.com/apache/ossie/discussions/18) +- [Relationship Semantics](https://github.com/apache/ossie/discussions/24) +- [Complex Relationship Definitions](https://github.com/apache/ossie/discussions/4) +- [Make Relationship Cardinality Explicit](https://github.com/apache/ossie/discussions/50) +- [Inner join in relationships](https://github.com/apache/ossie/discussions/11) +- [Support for cross-dataset dimensions & single-dataset measures](https://github.com/apache/ossie/discussions/27) +- [Semantic Filters](https://github.com/apache/ossie/discussions/5) +- [BIG IDEA: add metrics trees / input-output relations between metrics](https://github.com/apache/ossie/discussions/40) +- [Primary Key vs Unique Keys redundancy](https://github.com/apache/ossie/discussions/15) +- [Clarifying the Semantic Intent of Primary Keys vs. Unique Constraints](https://github.com/apache/ossie/discussions/119) **Roadmap Deliverables:** @@ -52,7 +71,7 @@ The current model lacks sufficient support for metrics at different grains, filt ### Catalog Integration & Semantic Services -**Goal:** Integrate OSI with data catalogs and enable centralized semantic services. +**Goal:** Integrate Ossie with data catalogs and enable centralized semantic services. **Motivation:** Semantic models need to be discoverable, governable, and shareable across systems. @@ -61,38 +80,38 @@ Semantic models need to be discoverable, governable, and shareable across system - Integration patterns with catalogs (e.g., Polaris) - Standalone semantic service / registry -- Discovery, versioning, and access control for OSI models +- Discovery, versioning, and access control for Ossie models **Related Issues:** -- [Issue #107 — Proposal: Adopt ontology-query as an Ontology Access Layer tool](https://github.com/open-semantic-interchange/OSI/issues/107) +- [Issue #107 — Proposal: Adopt ontology-query as an Ontology Access Layer tool](https://github.com/apache/ossie/issues/107) --- ### Ontology & Semantic Interoperability -**Goal:** Enable OSI to describe business concepts independently of physical data layout, supporting ontology-based semantic models and cross-model conceptual alignment. +**Goal:** Enable Ossie to describe business concepts independently of physical data layout, supporting ontology-based semantic models and cross-model conceptual alignment. **Motivation:** -Many semantic representations (e.g., Palantir, Goldman Sachs Legend) use ontologies to define meaning, and dimensional semantic models naturally layer on top of these. OSI currently solves structural interoperability — any tool can read and write semantic models in a common format — but it does not yet solve conceptual interoperability, where different models may describe the same business concept using different names or structures. An ontology layer would let organizations define canonical business concepts (Customer, Order, Product, etc.) independently of where the data lives and map physical semantic models back to shared definitions. +Many semantic representations (e.g., Palantir, Goldman Sachs Legend) use ontologies to define meaning, and dimensional semantic models naturally layer on top of these. Ossie currently solves structural interoperability — any tool can read and write semantic models in a common format — but it does not yet solve conceptual interoperability, where different models may describe the same business concept using different names or structures. An ontology layer would let organizations define canonical business concepts (Customer, Order, Product, etc.) independently of where the data lives and map physical semantic models back to shared definitions. **Key Discussions:** -- [Proposal: Extend OSI spec to interchange with semantic models based on relational ontologies](https://github.com/open-semantic-interchange/OSI/discussions/22) -- [Support for Ontologies](https://github.com/open-semantic-interchange/OSI/discussions/101) -- [Shared Semantics OSI](https://github.com/open-semantic-interchange/OSI/discussions/108) -- [Plans to support other data models than tabular data?](https://github.com/open-semantic-interchange/OSI/discussions/68) +- [Proposal: Extend Ossie spec to interchange with semantic models based on relational ontologies](https://github.com/apache/ossie/discussions/22) +- [Support for Ontologies](https://github.com/apache/ossie/discussions/101) +- [Shared Semantics Ossie](https://github.com/apache/ossie/discussions/108) +- [Plans to support other data models than tabular data?](https://github.com/apache/ossie/discussions/68) **Roadmap Deliverables:** - Ontology layer describing business concepts above the physical/logical semantic model -- Schema mappings between ontology concepts and OSI datasets/fields +- Schema mappings between ontology concepts and Ossie datasets/fields - Support for relational ontologies and non-tabular data models - Shared semantic definitions enabling conceptual interoperability across models **Related Issues:** -- [Issue #107 — Proposal: Adopt ontology-query as an Ontology Access Layer tool](https://github.com/open-semantic-interchange/OSI/issues/107) +- [Issue #107 — Proposal: Adopt ontology-query as an Ontology Access Layer tool](https://github.com/apache/ossie/issues/107) --- @@ -111,11 +130,11 @@ Users want reusable semantic models independent of underlying tables or views. **Key Discussions:** -- [Add support for "Logical Datasets" (query-defined entities / view definitions)](https://github.com/open-semantic-interchange/OSI/discussions/49) -- [Support one-to-many binding between a logical dataset and the physical table, view, or query](https://github.com/open-semantic-interchange/OSI/discussions/61) -- [Structured Dataset Sources](https://github.com/open-semantic-interchange/OSI/discussions/23) -- [Structured Dataset 'source' representation](https://github.com/open-semantic-interchange/OSI/discussions/109) -- [Support for reusable datasets and relationships across semantic models](https://github.com/open-semantic-interchange/OSI/discussions/103) +- [Add support for "Logical Datasets" (query-defined entities / view definitions)](https://github.com/apache/ossie/discussions/49) +- [Support one-to-many binding between a logical dataset and the physical table, view, or query](https://github.com/apache/ossie/discussions/61) +- [Structured Dataset Sources](https://github.com/apache/ossie/discussions/23) +- [Structured Dataset 'source' representation](https://github.com/apache/ossie/discussions/109) +- [Support for reusable datasets and relationships across semantic models](https://github.com/apache/ossie/discussions/103) **Roadmap Deliverables:** @@ -125,45 +144,45 @@ Users want reusable semantic models independent of underlying tables or views. **Related Issues:** -- [Issue #104 — First-class representation of file-backed datasets (e.g. Parquet)](https://github.com/open-semantic-interchange/OSI/issues/104) +- [Issue #104 — First-class representation of file-backed datasets (e.g. Parquet)](https://github.com/apache/ossie/issues/104) --- ### Semantic Query Language & Reference Engine -**Goal:** Define a standard query interface for interacting with OSI models and provide a canonical implementation for interpreting and executing them. +**Goal:** Define a standard query interface for interacting with Ossie models and provide a canonical implementation for interpreting and executing them. **Motivation:** Consumers (BI tools, AI systems, APIs) need a consistent way to query semantic models independent of underlying SQL dialects. A reference engine ensures consistent interpretation of the spec and accelerates ecosystem adoption. **Roadmap Deliverables:** -- Standard semantic query language (OSI-native or SQL-extended) +- Standard semantic query language (Ossie-native or SQL-extended) - Mapping from semantic queries → execution plans - Support for metrics, dimensions, filters, and relationships -- Reference compiler from OSI → SQL +- Reference compiler from Ossie → SQL - Canonical handling of joins, aggregations, and filters - Test suite to validate conformance across implementations **Related Issues:** -- [Issue #107 — Proposal: Adopt ontology-query as an Ontology Access Layer tool](https://github.com/open-semantic-interchange/OSI/issues/107) +- [Issue #107 — Proposal: Adopt ontology-query as an Ontology Access Layer tool](https://github.com/apache/ossie/issues/107) --- ### SQL Dialect, Expressions, and Execution Boundaries -**Goal:** Clarify the role of SQL and execution within OSI. +**Goal:** Clarify the role of SQL and execution within Ossie. **Motivation:** There is tension between portability and practical execution requirements. **Key Discussions:** -- [Add Default Dialect at Dataset Level](https://github.com/open-semantic-interchange/OSI/discussions/16) -- [Expectations around SQL expression dialects and conversion](https://github.com/open-semantic-interchange/OSI/discussions/28) -- [Use templating engine instead of plain yaml](https://github.com/open-semantic-interchange/OSI/discussions/62) -- [Jinja Templates](https://github.com/open-semantic-interchange/OSI/discussions/6) +- [Add Default Dialect at Dataset Level](https://github.com/apache/ossie/discussions/16) +- [Expectations around SQL expression dialects and conversion](https://github.com/apache/ossie/discussions/28) +- [Use templating engine instead of plain yaml](https://github.com/apache/ossie/discussions/62) +- [Jinja Templates](https://github.com/apache/ossie/discussions/6) **Roadmap Deliverables:** @@ -173,7 +192,7 @@ There is tension between portability and practical execution requirements. **Related Issues:** -- [Issue #52 — Only allow one dialect per OSI document](https://github.com/open-semantic-interchange/OSI/issues/52) +- [Issue #52 — Only allow one dialect per Ossie document](https://github.com/apache/ossie/issues/52) --- @@ -186,11 +205,11 @@ Inconsistent handling of hierarchies and time impacts usability and interoperabi **Key Discussions:** -- [Dimension Hierarchies](https://github.com/open-semantic-interchange/OSI/discussions/21) -- [Dimension Groups](https://github.com/open-semantic-interchange/OSI/discussions/20) -- [Replace is_time with dimension_type Enum](https://github.com/open-semantic-interchange/OSI/discussions/17) -- [Universal calendar support](https://github.com/open-semantic-interchange/OSI/discussions/44) -- [Date Spine models](https://github.com/open-semantic-interchange/OSI/discussions/47) +- [Dimension Hierarchies](https://github.com/apache/ossie/discussions/21) +- [Dimension Groups](https://github.com/apache/ossie/discussions/20) +- [Replace is_time with dimension_type Enum](https://github.com/apache/ossie/discussions/17) +- [Universal calendar support](https://github.com/apache/ossie/discussions/44) +- [Date Spine models](https://github.com/apache/ossie/discussions/47) **Roadmap Deliverables:** @@ -200,23 +219,23 @@ Inconsistent handling of hierarchies and time impacts usability and interoperabi **Related Issues:** -- [Issue #84 — Support field datatype rather than is_time](https://github.com/open-semantic-interchange/OSI/issues/84) +- [Issue #84 — Support field datatype rather than is_time](https://github.com/apache/ossie/issues/84) --- ### AI-Native Semantic Layer -**Goal:** Enable OSI as a reliable foundation for AI-driven analytics. +**Goal:** Enable Ossie as a reliable foundation for AI-driven analytics. **Motivation:** There is growing demand for structured semantic context and grounded query generation. **Key Discussions:** -- [Do not prescribe "AI Context" as a key name](https://github.com/open-semantic-interchange/OSI/discussions/32) -- [Keyword for skipping context for AI](https://github.com/open-semantic-interchange/OSI/discussions/14) -- [Usage guidelines with samples especially for ai_context field](https://github.com/open-semantic-interchange/OSI/discussions/9) -- [Add verified_queries as a core element of the spec](https://github.com/open-semantic-interchange/OSI/discussions/82) +- [Do not prescribe "AI Context" as a key name](https://github.com/apache/ossie/discussions/32) +- [Keyword for skipping context for AI](https://github.com/apache/ossie/discussions/14) +- [Usage guidelines with samples especially for ai_context field](https://github.com/apache/ossie/discussions/9) +- [Add verified_queries as a core element of the spec](https://github.com/apache/ossie/discussions/82) **Roadmap Deliverables:** @@ -235,11 +254,11 @@ Enterprise adoption requires consistent identifiers, validation, and governance **Key Discussions:** -- [Make stable identifiers explicit rather than reusing name](https://github.com/open-semantic-interchange/OSI/discussions/31) -- [Metrics schema - Certified and Certifying Authority](https://github.com/open-semantic-interchange/OSI/discussions/53) -- [Governance metadata hooks](https://github.com/open-semantic-interchange/OSI/discussions/13) -- [Add more rigor to the spec using LinkML](https://github.com/open-semantic-interchange/OSI/discussions/67) -- [OSI-level validations?](https://github.com/open-semantic-interchange/OSI/discussions/35) +- [Make stable identifiers explicit rather than reusing name](https://github.com/apache/ossie/discussions/31) +- [Metrics schema - Certified and Certifying Authority](https://github.com/apache/ossie/discussions/53) +- [Governance metadata hooks](https://github.com/apache/ossie/discussions/13) +- [Add more rigor to the spec using LinkML](https://github.com/apache/ossie/discussions/67) +- [Ossie-level validations?](https://github.com/apache/ossie/discussions/35) **Roadmap Deliverables:** @@ -249,9 +268,9 @@ Enterprise adoption requires consistent identifiers, validation, and governance **Related Issues:** -- [Issue #102 — Add semantic versioning and Git releases for core-spec/osi-schema.json](https://github.com/open-semantic-interchange/OSI/issues/102) -- [Issue #92 — Community Implementation: Trust Control Center — OSI-compatible governance & reconciliation platform](https://github.com/open-semantic-interchange/OSI/issues/92) -- [Issue #87 — New Flags: Restricted and Internal only indicators](https://github.com/open-semantic-interchange/OSI/issues/87) +- [Issue #102 — Add semantic versioning and Git releases for core-spec/osi-schema.json](https://github.com/apache/ossie/issues/102) +- [Issue #92 — Community Implementation: Trust Control Center — Ossie-compatible governance & reconciliation platform](https://github.com/apache/ossie/issues/92) +- [Issue #87 — New Flags: Restricted and Internal only indicators](https://github.com/apache/ossie/issues/87) --- @@ -266,7 +285,7 @@ Organizations repeatedly recreate similar semantic models (e.g., SaaS, finance, - Curated domain-specific semantic model templates - Best-practice metric and dimension definitions by industry -- Interoperable model packages aligned with OSI +- Interoperable model packages aligned with Ossie --- @@ -278,10 +297,10 @@ These items improve usability, clarity, and completeness without fundamentally c ### Naming, Terminology, and UX Improvements -**Goal:** Align OSI vocabulary with how practitioners think about semantic models, and improve the authoring experience. +**Goal:** Align Ossie vocabulary with how practitioners think about semantic models, and improve the authoring experience. **Motivation:** -Several naming conventions in the current spec create confusion or clash with established industry terminology. Clearer naming reduces onboarding friction and improves readability of OSI definitions. +Several naming conventions in the current spec create confusion or clash with established industry terminology. Clearer naming reduces onboarding friction and improves readability of Ossie definitions. **Roadmap Deliverables:** @@ -290,10 +309,10 @@ Several naming conventions in the current spec create confusion or clash with es **Key Discussions:** -- [Rename Field to Dimension](https://github.com/open-semantic-interchange/OSI/discussions/33) -- [Rename Dataset.source to avoid conflation with where an entity was first defined](https://github.com/open-semantic-interchange/OSI/discussions/34) -- [Generalise description field](https://github.com/open-semantic-interchange/OSI/discussions/36) -- [Introduce a concept for "Display name"](https://github.com/open-semantic-interchange/OSI/discussions/37) +- [Rename Field to Dimension](https://github.com/apache/ossie/discussions/33) +- [Rename Dataset.source to avoid conflation with where an entity was first defined](https://github.com/apache/ossie/discussions/34) +- [Generalise description field](https://github.com/apache/ossie/discussions/36) +- [Introduce a concept for "Display name"](https://github.com/apache/ossie/discussions/37) --- @@ -311,47 +330,47 @@ Consuming systems (BI tools, AI agents, dashboards) frequently need to know whet **Key Discussions:** -- [Native support for units](https://github.com/open-semantic-interchange/OSI/discussions/42) -- [Native support for currencies](https://github.com/open-semantic-interchange/OSI/discussions/43) -- [Semantic Field Types: dimension_type, data_type, and pii_classification](https://github.com/open-semantic-interchange/OSI/discussions/55) -- [Add portable field physical metadata to OSI](https://github.com/open-semantic-interchange/OSI/discussions/110) +- [Native support for units](https://github.com/apache/ossie/discussions/42) +- [Native support for currencies](https://github.com/apache/ossie/discussions/43) +- [Semantic Field Types: dimension_type, data_type, and pii_classification](https://github.com/apache/ossie/discussions/55) +- [Add portable field physical metadata to Ossie](https://github.com/apache/ossie/discussions/110) **Related Issues:** -- [Issue #58 — New attribute: contain personal data](https://github.com/open-semantic-interchange/OSI/issues/58) -- [Issue #59 — New attribute: confidential indicator](https://github.com/open-semantic-interchange/OSI/issues/59) +- [Issue #58 — New attribute: contain personal data](https://github.com/apache/ossie/issues/58) +- [Issue #59 — New attribute: confidential indicator](https://github.com/apache/ossie/issues/59) --- -### Extended Metadata for OSI +### Extended Metadata for Apache Ossie **Goal:** Introduce a lightweight, optional metadata layer that improves how data is interpreted, presented, and consumed — without affecting execution semantics. **Motivation:** -OSI standardizes structural and logical semantics well, but there is limited support for conveying interpretability context such as display conventions, default aggregation behavior, KPI polarity, sorting preferences, and alignment to external semantic concepts. These details are often redefined or inferred inconsistently across developers, BI tools, and AI systems. +Ossie standardizes structural and logical semantics well, but there is limited support for conveying interpretability context such as display conventions, default aggregation behavior, KPI polarity, sorting preferences, and alignment to external semantic concepts. These details are often redefined or inferred inconsistently across developers, BI tools, and AI systems. **Roadmap Deliverables:** -- [Extended Metadata Proposal for OSI](https://github.com/open-semantic-interchange/OSI/issues/100) — optional, backward-compatible metadata fields (e.g., `measurement`, `display_format`, `semantic_type`, `default_aggregation`, `desired_direction`, `default_sort`, `semantic_mappings`) +- [Extended Metadata Proposal for Ossie](https://github.com/apache/ossie/issues/100) — optional, backward-compatible metadata fields (e.g., `measurement`, `display_format`, `semantic_type`, `default_aggregation`, `desired_direction`, `default_sort`, `semantic_mappings`) - Richer application-specific extension points beyond `custom_extensions` - Sample value annotations for documentation and AI grounding **Key Discussions:** -- [Expand custom_extensions to be more suitable for application-specific metadata](https://github.com/open-semantic-interchange/OSI/discussions/30) -- [Sample values](https://github.com/open-semantic-interchange/OSI/discussions/7) -- [Governance metadata hooks](https://github.com/open-semantic-interchange/OSI/discussions/13) *(also informs strategic governance work)* -- [Optional "positive direction" on metrics](https://github.com/open-semantic-interchange/OSI/discussions/41) -- [Add default_aggregation to Field](https://github.com/open-semantic-interchange/OSI/discussions/115) +- [Expand custom_extensions to be more suitable for application-specific metadata](https://github.com/apache/ossie/discussions/30) +- [Sample values](https://github.com/apache/ossie/discussions/7) +- [Governance metadata hooks](https://github.com/apache/ossie/discussions/13) *(also informs strategic governance work)* +- [Optional "positive direction" on metrics](https://github.com/apache/ossie/discussions/41) +- [Add default_aggregation to Field](https://github.com/apache/ossie/discussions/115) --- ### Developer Experience & Documentation -**Goal:** Lower the barrier to adopting and correctly using OSI through better guidance, examples, and tooling-friendly formatting. +**Goal:** Lower the barrier to adopting and correctly using Ossie through better guidance, examples, and tooling-friendly formatting. **Motivation:** -New adopters and tool authors need clearer documentation, real-world samples, and support for rich-text descriptions to effectively author and consume OSI models. +New adopters and tool authors need clearer documentation, real-world samples, and support for rich-text descriptions to effectively author and consume Ossie models. **Roadmap Deliverables:** @@ -361,13 +380,13 @@ New adopters and tool authors need clearer documentation, real-world samples, an **Key Discussions:** -- [Usage guidelines with samples especially for ai_context field](https://github.com/open-semantic-interchange/OSI/discussions/9) -- [Information about data modelling](https://github.com/open-semantic-interchange/OSI/discussions/8) -- [Markdown support](https://github.com/open-semantic-interchange/OSI/discussions/38) +- [Usage guidelines with samples especially for ai_context field](https://github.com/apache/ossie/discussions/9) +- [Information about data modelling](https://github.com/apache/ossie/discussions/8) +- [Markdown support](https://github.com/apache/ossie/discussions/38) **Existing Artifacts:** -- [Core Specification (spec.md)](core-spec/spec.md) — the current OSI spec document +- [Core Specification (spec.md)](core-spec/spec.md) — the current Ossie spec document - [TPC-DS Example Model](examples/tpcds_semantic_model.yaml) — reference semantic model using the TPC-DS benchmark - [Converter Guide (converters/index.md)](converters/index.md) — hub-and-spoke converter architecture and authoring guide @@ -375,7 +394,7 @@ New adopters and tool authors need clearer documentation, real-world samples, an ### Specialized Capabilities -**Goal:** Extend OSI to support domain-specific data types, audience definitions, and patterns that go beyond traditional tabular analytics. +**Goal:** Extend Ossie to support domain-specific data types, audience definitions, and patterns that go beyond traditional tabular analytics. **Motivation:** Geospatial analytics, time-series modeling, and audience segmentation have unique requirements that benefit from first-class spec support rather than ad-hoc workarounds. @@ -388,36 +407,36 @@ Geospatial analytics, time-series modeling, and audience segmentation have uniqu **Key Discussions:** -- [Geospatial data support: spatial field types, spatial relationships, and geographic hierarchies](https://github.com/open-semantic-interchange/OSI/discussions/69) -- [Date Spine models](https://github.com/open-semantic-interchange/OSI/discussions/47) -- [Add Support for Audiences](https://github.com/open-semantic-interchange/OSI/discussions/51) -- [Spatial dimension type: extending dimension with a spatial descriptor for geometry/geography and spatial index data](https://github.com/open-semantic-interchange/OSI/discussions/114) +- [Geospatial data support: spatial field types, spatial relationships, and geographic hierarchies](https://github.com/apache/ossie/discussions/69) +- [Date Spine models](https://github.com/apache/ossie/discussions/47) +- [Add Support for Audiences](https://github.com/apache/ossie/discussions/51) +- [Spatial dimension type: extending dimension with a spatial descriptor for geometry/geography and spatial index data](https://github.com/apache/ossie/discussions/114) --- ### Tooling & Ecosystem Support -**Goal:** Provide reference tooling that makes it easy to validate, convert, and adopt OSI models. +**Goal:** Provide reference tooling that makes it easy to validate, convert, and adopt Ossie models. **Motivation:** -Broad ecosystem adoption depends on practical tools that let teams validate their models against the spec and convert between OSI and existing vendor formats without manual effort. +Broad ecosystem adoption depends on practical tools that let teams validate their models against the spec and convert between Ossie and existing vendor formats without manual effort. **Roadmap Deliverables:** - Validator code (schema validation, linting, conformance checks) -- Participant ↔ OSI converter code (read/write interoperability with existing tools) +- Participant ↔ Ossie converter code (read/write interoperability with existing tools) **Existing Artifacts:** - [JSON Schema (osi-schema.json)](core-spec/osi-schema.json) — schema for structural validation -- [Validation Script (validate.py)](validation/validate.py) — validates OSI YAML against JSON Schema, unique names, references, and SQL syntax -- [Snowflake Converter](converters/snowflake/) — OSI → Snowflake Cortex Analyst YAML converter -- [GoodData Converter](converters/gooddata/) — bidirectional OSI ↔ GoodData LDM converter -- [Salesforce Converter](converters/salesforce/) — OSI ↔ Salesforce converter -- [Apache Polaris Converter](converters/polaris/) — OSI → Apache Polaris converter +- [Validation Script (validate.py)](validation/validate.py) — validates Ossie YAML against JSON Schema, unique names, references, and SQL syntax +- [Snowflake Converter](converters/snowflake/) — Ossie → Snowflake Cortex Analyst YAML converter +- [GoodData Converter](converters/gooddata/) — bidirectional Ossie ↔ GoodData LDM converter +- [Salesforce Converter](converters/salesforce/) — Ossie ↔ Salesforce converter +- [Apache Polaris Converter](converters/polaris/) — Ossie → Apache Polaris converter **Related Issues:** -- [Issue #121 — Create converter/common module (for Java binding)](https://github.com/open-semantic-interchange/OSI/issues/121) -- [Issue #111 — Follow up on OSI ai_context and custom_extensions mapping in Snowflake YAML](https://github.com/open-semantic-interchange/OSI/issues/111) +- [Issue #121 — Create converter/common module (for Java binding)](https://github.com/apache/ossie/issues/121) +- [Issue #111 — Follow up on Ossie ai_context and custom_extensions mapping in Snowflake YAML](https://github.com/apache/ossie/issues/111) diff --git a/converters/dbt/README.md b/converters/dbt/README.md index ae31a011..e78663e4 100644 --- a/converters/dbt/README.md +++ b/converters/dbt/README.md @@ -1,11 +1,30 @@ -# osi-dbt + + +# apache-ossie-dbt + +Converts between dbt's [MetricFlow Semantic Interface](https://docs.getdbt.com/docs/build/about-metricflow) (MSI) and the [Apache Ossie](https://github.com/apache/ossie) format. Both conversion directions are supported: -- `msi-to-osi` — `semantic_manifest.json` (dbt output) → OSI YAML -- `osi-to-msi` — OSI YAML → `semantic_manifest.json` +- `msi-to-osi` — `semantic_manifest.json` (dbt output) → Ossie YAML +- `osi-to-msi` — Ossie YAML → `semantic_manifest.json` ## Requirements @@ -15,18 +34,18 @@ Both conversion directions are supported: ## Installation ```bash -pip install osi-dbt +pip install apache-ossie-dbt ``` Or with uv: ```bash -uv add osi-dbt +uv add apache-ossie-dbt ``` ## CLI usage -### dbt → OSI +### dbt → Apache Ossie Generate `semantic_manifest.json` from your dbt project first: @@ -35,24 +54,24 @@ dbt parse # output: target/semantic_manifest.json ``` -Then convert to OSI YAML: +Then convert to Ossie YAML: ```bash -osi-dbt msi-to-osi -i target/semantic_manifest.json -o semantic_model.yaml +ossie-dbt msi-to-osi -i target/semantic_manifest.json -o semantic_model.yaml ``` -By default the OSI semantic model is named `semantic_model`. Override it with `--model-name`: +By default the Ossie semantic model is named `semantic_model`. Override it with `--model-name`: ```bash -osi-dbt msi-to-osi -i target/semantic_manifest.json -o semantic_model.yaml --model-name my_project +ossie-dbt msi-to-osi -i target/semantic_manifest.json -o semantic_model.yaml --model-name my_project ``` Conversion issues (e.g. dropped CONVERSION or PRIVATE metrics) are printed as warnings to stderr. The output file is still written. -### OSI → dbt +### Apache Ossie → dbt ```bash -osi-dbt osi-to-msi -i semantic_model.yaml -o semantic_manifest.json +ossie-dbt osi-to-msi -i semantic_model.yaml -o semantic_manifest.json ``` Produces a `semantic_manifest.json` that metricflow can load. @@ -60,18 +79,18 @@ Produces a `semantic_manifest.json` that metricflow can load. ### Help ```bash -osi-dbt --help -osi-dbt msi-to-osi --help -osi-dbt osi-to-msi --help +ossie-dbt --help +ossie-dbt msi-to-osi --help +ossie-dbt osi-to-msi --help ``` ## Python API ```python -from osi_dbt import MSIToOSIConverter, OSIToMSIConverter +from ossie_dbt import MSIToOSIConverter, OSIToMSIConverter from metricflow_semantics.model.dbt_manifest_parser import parse_manifest_from_dbt_generated_manifest -# dbt → OSI +# dbt → Apache Ossie manifest = parse_manifest_from_dbt_generated_manifest(Path("target/semantic_manifest.json").read_text()) result = MSIToOSIConverter().convert(manifest, osi_model_name="my_project") @@ -80,9 +99,9 @@ for issue in result.issues: osi_yaml = result.output.to_osi_yaml() -# OSI → dbt +# Apache Ossie → dbt import yaml -from osi import OSIDocument +from ossie import OSIDocument document = OSIDocument.model_validate(yaml.safe_load(Path("semantic_model.yaml").read_text())) result = OSIToMSIConverter().convert(document) @@ -91,21 +110,21 @@ manifest_json = result.output.model_dump_json(by_alias=True, exclude_none=True, ### Conversion notes -**MSI → OSI** is lossy in the following ways, each recorded as a `ConverterIssue` in the result: +**MSI → Ossie** is lossy in the following ways, each recorded as a `ConverterIssue` in the result: | Issue type | Reason | |---|---| -| `CONVERSION_METRIC_DROPPED` | OSI has no conversion-funnel metric type | -| `PRIVATE_METRIC_DROPPED` | OSI has no visibility modifiers | -| `NATURAL_ENTITY_DROPPED` | OSI has no natural-key entity type | -| `CUMULATIVE_SEMANTICS_LOSS` | Window/grain semantics cannot be expressed in an OSI expression string; the base aggregation is preserved | +| `CONVERSION_METRIC_DROPPED` | Ossie has no conversion-funnel metric type | +| `PRIVATE_METRIC_DROPPED` | Ossie has no visibility modifiers | +| `NATURAL_ENTITY_DROPPED` | Ossie has no natural-key entity type | +| `CUMULATIVE_SEMANTICS_LOSS` | Window/grain semantics cannot be expressed in an Ossie expression string; the base aggregation is preserved | -**OSI → MSI** reconstructs a best-effort MSI manifest from OSI's simpler schema. Nothing is dropped, but OSI carries less structural information than MSI, so the converter makes the following choices: +**Ossie → MSI** reconstructs a best-effort MSI manifest from Ossie's simpler schema. Nothing is dropped, but Ossie carries less structural information than MSI, so the converter makes the following choices: - Single aggregations (`SUM(col)`, `COUNT(DISTINCT col)`, etc.) → SIMPLE metric with `metric_aggregation_params` - `(expr_a) / (expr_b)` → RATIO metric with auto-generated sub-metrics - Anything else → SIMPLE metric with the raw expression stored verbatim -- Time dimensions always receive `TimeGranularity.DAY` (OSI carries no granularity field) +- Time dimensions always receive `TimeGranularity.DAY` (Ossie carries no granularity field) ## Development diff --git a/converters/dbt/pyproject.toml b/converters/dbt/pyproject.toml index f1bb3b0e..812b80db 100644 --- a/converters/dbt/pyproject.toml +++ b/converters/dbt/pyproject.toml @@ -1,15 +1,36 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + [build-system] requires = ["hatchling"] build-backend = "hatchling.build" [project] -name = "osi-dbt" +name = "apache-ossie-dbt" version = "0.2.0.dev0" -description = "dbt (MetricFlow Semantic Interface) <> OSI converter" +description = "dbt (MetricFlow Semantic Interface) <> Apache Ossie converter" requires-python = ">=3.11" +classifiers = [ + "License :: OSI Approved :: Apache Software License", + "Programming Language :: Python :: 3", +] dependencies = [ - "osi-python>=0.2.0.dev0", - "metricflow>=0.200", + "apache-ossie>=0.2.0.dev0", + "metricflow>=0.209.0", "sqlglot>=20.0", "jinja2>=3.0", "PyYAML>=6.0", @@ -19,10 +40,10 @@ dependencies = [ text = "Apache-2.0" [project.scripts] -osi-dbt = "osi_dbt.cli:main" +ossie-dbt = "ossie_dbt.cli:main" [tool.hatch.build.targets.wheel] -packages = ["src/osi_dbt"] +packages = ["src/ossie_dbt"] [tool.uv] dev-dependencies = [ diff --git a/converters/dbt/src/osi_dbt/__init__.py b/converters/dbt/src/osi_dbt/__init__.py deleted file mode 100644 index 5cc35cbb..00000000 --- a/converters/dbt/src/osi_dbt/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -from osi_dbt.converter_issues import ConverterIssue, ConverterIssueType, ConverterResult -from osi_dbt.msi_to_osi import MSIToOSIConverter -from osi_dbt.osi_to_msi import OSIToMSIConverter - -__all__ = [ - "ConverterIssue", - "ConverterIssueType", - "ConverterResult", - "MSIToOSIConverter", - "OSIToMSIConverter", -] diff --git a/converters/dbt/src/ossie_dbt/__init__.py b/converters/dbt/src/ossie_dbt/__init__.py new file mode 100644 index 00000000..f3f24928 --- /dev/null +++ b/converters/dbt/src/ossie_dbt/__init__.py @@ -0,0 +1,28 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +from ossie_dbt.converter_issues import ConverterIssue, ConverterIssueType, ConverterResult +from ossie_dbt.msi_to_osi import MSIToOSIConverter +from ossie_dbt.osi_to_msi import OSIToMSIConverter + +__all__ = [ + "ConverterIssue", + "ConverterIssueType", + "ConverterResult", + "MSIToOSIConverter", + "OSIToMSIConverter", +] diff --git a/converters/dbt/src/osi_dbt/cli.py b/converters/dbt/src/ossie_dbt/cli.py similarity index 58% rename from converters/dbt/src/osi_dbt/cli.py rename to converters/dbt/src/ossie_dbt/cli.py index 5dd8a297..8d71faad 100644 --- a/converters/dbt/src/osi_dbt/cli.py +++ b/converters/dbt/src/ossie_dbt/cli.py @@ -1,8 +1,25 @@ -"""CLI entry point for the osi-dbt converter. +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +"""CLI entry point for the ossie-dbt converter. Usage: - osi-dbt msi-to-osi -i semantic_manifest.json -o output.yaml - osi-dbt osi-to-msi -i input.yaml -o semantic_manifest.json + ossie-dbt msi-to-osi -i semantic_manifest.json -o output.yaml + ossie-dbt osi-to-msi -i input.yaml -o semantic_manifest.json """ import argparse @@ -12,18 +29,18 @@ import yaml -from osi import OSIDocument -from osi_dbt.converter_issues import ConverterIssueType -from osi_dbt.msi_to_osi import MSIToOSIConverter -from osi_dbt.osi_to_msi import OSIToMSIConverter +from ossie import OSIDocument +from ossie_dbt.converter_issues import ConverterIssueType +from ossie_dbt.msi_to_osi import MSIToOSIConverter +from ossie_dbt.osi_to_msi import OSIToMSIConverter from metricflow_semantics.model.dbt_manifest_parser import parse_manifest_from_dbt_generated_manifest _ISSUE_REASON: dict[ConverterIssueType, str] = { - ConverterIssueType.CONVERSION_METRIC_DROPPED: "OSI has no conversion-funnel metric type", - ConverterIssueType.PRIVATE_METRIC_DROPPED: "OSI has no visibility modifiers", - ConverterIssueType.NATURAL_ENTITY_DROPPED: "OSI has no natural-key entity type", - ConverterIssueType.CUMULATIVE_SEMANTICS_LOSS: "OSI expressions cannot represent window or grain semantics; the base aggregation was preserved", + ConverterIssueType.CONVERSION_METRIC_DROPPED: "Ossie has no conversion-funnel metric type", + ConverterIssueType.PRIVATE_METRIC_DROPPED: "Ossie has no visibility modifiers", + ConverterIssueType.NATURAL_ENTITY_DROPPED: "Ossie has no natural-key entity type", + ConverterIssueType.CUMULATIVE_SEMANTICS_LOSS: "Ossie expressions cannot represent window or grain semantics; the base aggregation was preserved", } _DROPPED_ISSUE_TYPES = { @@ -64,20 +81,20 @@ def _cmd_osi_to_msi(args: argparse.Namespace) -> None: def main() -> None: parser = argparse.ArgumentParser( - prog="osi-dbt", - description="Convert between dbt semantic_manifest.json and OSI YAML.", + prog="ossie-dbt", + description="Convert between dbt semantic_manifest.json and Ossie YAML.", ) subparsers = parser.add_subparsers(dest="command", required=True) - msi_to_osi = subparsers.add_parser("msi-to-osi", help="Convert semantic_manifest.json → OSI YAML") + msi_to_osi = subparsers.add_parser("msi-to-osi", help="Convert semantic_manifest.json → Ossie YAML") msi_to_osi.add_argument("-i", "--input", required=True, metavar="FILE", help="Path to semantic_manifest.json") - msi_to_osi.add_argument("-o", "--output", required=True, metavar="FILE", help="Path for output OSI YAML") + msi_to_osi.add_argument("-o", "--output", required=True, metavar="FILE", help="Path for output Ossie YAML") msi_to_osi.add_argument( - "--model-name", default="semantic_model", metavar="NAME", help="OSI semantic model name (default: semantic_model)" + "--model-name", default="semantic_model", metavar="NAME", help="Ossie semantic model name (default: semantic_model)" ) - osi_to_msi = subparsers.add_parser("osi-to-msi", help="Convert OSI YAML → semantic_manifest.json") - osi_to_msi.add_argument("-i", "--input", required=True, metavar="FILE", help="Path to OSI YAML") + osi_to_msi = subparsers.add_parser("osi-to-msi", help="Convert Ossie YAML → semantic_manifest.json") + osi_to_msi.add_argument("-i", "--input", required=True, metavar="FILE", help="Path to Ossie YAML") osi_to_msi.add_argument("-o", "--output", required=True, metavar="FILE", help="Path for output semantic_manifest.json") args = parser.parse_args() diff --git a/converters/dbt/src/osi_dbt/converter_issues.py b/converters/dbt/src/ossie_dbt/converter_issues.py similarity index 52% rename from converters/dbt/src/osi_dbt/converter_issues.py rename to converters/dbt/src/ossie_dbt/converter_issues.py index 7c87862f..e1606fa1 100644 --- a/converters/dbt/src/osi_dbt/converter_issues.py +++ b/converters/dbt/src/ossie_dbt/converter_issues.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + from dataclasses import dataclass from enum import Enum from typing import Generic, List, TypeVar diff --git a/converters/dbt/src/osi_dbt/expression_utils.py b/converters/dbt/src/ossie_dbt/expression_utils.py similarity index 85% rename from converters/dbt/src/osi_dbt/expression_utils.py rename to converters/dbt/src/ossie_dbt/expression_utils.py index a9dfbd22..8e481df8 100644 --- a/converters/dbt/src/osi_dbt/expression_utils.py +++ b/converters/dbt/src/ossie_dbt/expression_utils.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + from typing import Optional, Tuple import sqlglot diff --git a/converters/dbt/src/osi_dbt/filter_utils.py b/converters/dbt/src/ossie_dbt/filter_utils.py similarity index 83% rename from converters/dbt/src/osi_dbt/filter_utils.py rename to converters/dbt/src/ossie_dbt/filter_utils.py index a10642f5..9875999d 100644 --- a/converters/dbt/src/osi_dbt/filter_utils.py +++ b/converters/dbt/src/ossie_dbt/filter_utils.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + from typing import List, Optional, Sequence import jinja2 @@ -97,7 +114,7 @@ def _render_filter_template(template: str) -> str: `{{ TimeDimension('order__ds', 'day') }}`, `{{ Entity('user') }}`, and `{{ Metric('revenue') }}` are resolved to their column-name equivalents using lightweight stubs. The output is a best-effort SQL - string suitable for embedding in an OSI expression. + string suitable for embedding in an Ossie expression. """ return jinja2.Template(template, undefined=jinja2.StrictUndefined).render( Dimension=_DimensionStub, @@ -115,7 +132,7 @@ def _collect_filter_sql(*filters: Optional[WhereFilterIntersection]) -> Optional as `order__status`. These are *not* fully resolved SQL column aliases — resolving to actual table column names would require `WhereFilterSpecFactory` and `ColumnAssociationResolver` from `metricflow_semantics`, which is out - of scope here. OSI consumers are expected to perform their own column + of scope here. Ossie consumers are expected to perform their own column resolution against the source data. """ parts: List[str] = [] diff --git a/converters/dbt/src/osi_dbt/msi_to_osi.py b/converters/dbt/src/ossie_dbt/msi_to_osi.py similarity index 93% rename from converters/dbt/src/osi_dbt/msi_to_osi.py rename to converters/dbt/src/ossie_dbt/msi_to_osi.py index 6a7dbec4..db9e1315 100644 --- a/converters/dbt/src/osi_dbt/msi_to_osi.py +++ b/converters/dbt/src/ossie_dbt/msi_to_osi.py @@ -1,10 +1,27 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + import re from collections import defaultdict from dataclasses import dataclass from itertools import combinations from typing import Dict, List, Optional, Sequence, Tuple -from osi import ( +from ossie import ( OSIDataset, OSIDialect, OSIDialectExpression, @@ -16,8 +33,8 @@ OSIRelationship, OSISemanticModel, ) -from osi_dbt.converter_issues import ConverterIssue, ConverterIssueType, ConverterResult -from osi_dbt.filter_utils import _collect_filter_sql, _merge_filter_sqls +from ossie_dbt.converter_issues import ConverterIssue, ConverterIssueType, ConverterResult +from ossie_dbt.filter_utils import _collect_filter_sql, _merge_filter_sqls from metricflow_semantic_interfaces.enum_extension import assert_values_exhausted from metricflow_semantic_interfaces.implementations.semantic_manifest import PydanticSemanticManifest @@ -57,7 +74,7 @@ class _RelationshipDirection: class MSIToOSIConverter: - """Converts an MSI SemanticManifest into an OSI Document.""" + """Converts an MSI SemanticManifest into an Ossie Document.""" def __init__(self, dialect: OSIDialect = OSIDialect.ANSI_SQL) -> None: self._dialect = dialect @@ -265,7 +282,7 @@ def _resolve_cumulative( ) -> str: """Resolve a CUMULATIVE metric to its base aggregation expression. - Window/grain semantics are not representable in an OSI expression string. + Window/grain semantics are not representable in an Ossie expression string. """ cumulative_params = metric.type_params.cumulative_type_params if cumulative_params is None or cumulative_params.metric is None: @@ -355,9 +372,9 @@ def _build_entity_index( def _relationship_direction( ds_a: str, col_a: str, type_a: EntityType, ds_b: str, col_b: str, type_b: EntityType ) -> _RelationshipDirection: - """Return a _RelationshipDirection obeying OSI directionality. + """Return a _RelationshipDirection obeying Ossie directionality. - OSI spec: `from` is the many-side (FK holder), `to` is the one-side (PK holder). + Ossie spec: `from` is the many-side (FK holder), `to` is the one-side (PK holder). FOREIGN entities are always the many-side; PRIMARY/UNIQUE are the one-side. When both sides share the same cardinality tier, break ties alphabetically by dataset name. """ @@ -378,7 +395,7 @@ def _relationship_direction( def _build_relationships( entity_index: Dict[str, List[_EntityEntry]], ) -> List[OSIRelationship]: - """Resolve implicit MSI entity links into explicit OSI relationships. + """Resolve implicit MSI entity links into explicit Ossie relationships. Every pair of datasets sharing an entity name is a valid join path. """ diff --git a/converters/dbt/src/osi_dbt/osi_to_msi.py b/converters/dbt/src/ossie_dbt/osi_to_msi.py similarity index 92% rename from converters/dbt/src/osi_dbt/osi_to_msi.py rename to converters/dbt/src/ossie_dbt/osi_to_msi.py index 5eb9e493..1da337de 100644 --- a/converters/dbt/src/osi_dbt/osi_to_msi.py +++ b/converters/dbt/src/ossie_dbt/osi_to_msi.py @@ -1,7 +1,24 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + from dataclasses import dataclass from typing import List, Optional, Set -from osi import ( +from ossie import ( OSIDataset, OSIDialect, OSIDocument, @@ -9,8 +26,8 @@ OSIField, OSISemanticModel, ) -from osi_dbt.converter_issues import ConverterResult -from osi_dbt.expression_utils import ( +from ossie_dbt.converter_issues import ConverterResult +from ossie_dbt.expression_utils import ( _extract_agg_info, _get_raw_inner_col, _strip_qualifier, @@ -58,9 +75,9 @@ class _KeySets: class OSIToMSIConverter: - """Converts an OSI Document into a PydanticSemanticManifest. + """Converts an Ossie Document into a PydanticSemanticManifest. - The conversion is inherently lossy: OSI stores metrics as raw SQL expressions + The conversion is inherently lossy: Ossie stores metrics as raw SQL expressions and carries no metric-type metadata (SIMPLE / RATIO / CUMULATIVE / …). The converter reconstructs a best-effort MSI manifest using the following rules: @@ -68,7 +85,7 @@ class OSIToMSIConverter: * Fields are classified as entities or dimensions using key and relationship metadata. Aggregation info now lives directly on metrics (via `metric_aggregation_params`), not on semantic model measures. - * Time dimensions always receive `TimeGranularity.DAY` — OSI has no + * Time dimensions always receive `TimeGranularity.DAY` — Ossie has no granularity field. * Metric expressions are parsed with sqlglot: - single-agg patterns (`SUM(col)`, `COUNT(DISTINCT col)`, …) → SIMPLE @@ -159,7 +176,7 @@ def _classify_field( entities: List[PydanticEntity], dimensions: List[PydanticDimension], ) -> None: - """Classify a single OSI field and append it to the appropriate list. + """Classify a single Ossie field and append it to the appropriate list. Classification order (first match wins): 1. primary_key → PRIMARY entity @@ -211,7 +228,7 @@ def _classify_field( ) return if field.dimension is not None and field.dimension.is_time: - # OSI carries no granularity metadata; default to DAY. + # Ossie carries no granularity metadata; default to DAY. dimensions.append( PydanticDimension( name=field.name, @@ -254,7 +271,7 @@ def _convert_metric( description: Optional[str], datasets: List[OSIDataset], ) -> List[PydanticMetric]: - """Return one or more PydanticMetric objects for the given OSI expression. + """Return one or more PydanticMetric objects for the given Ossie expression. Simple metrics use `metric_aggregation_params` to store aggregation type and column expression directly — no intermediate measure is created. diff --git a/converters/dbt/tests/__init__.py b/converters/dbt/tests/__init__.py index e69de29b..24569233 100644 --- a/converters/dbt/tests/__init__.py +++ b/converters/dbt/tests/__init__.py @@ -0,0 +1,17 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + diff --git a/converters/dbt/tests/helpers.py b/converters/dbt/tests/helpers.py index 534b4587..550a4937 100644 --- a/converters/dbt/tests/helpers.py +++ b/converters/dbt/tests/helpers.py @@ -1,6 +1,23 @@ -"""Shared test helpers for OSI converter tests.""" - -from osi import ( +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +"""Shared test helpers for Ossie converter tests.""" + +from ossie import ( OSIDataset, OSIDialect, OSIDialectExpression, @@ -136,7 +153,7 @@ def _filter(sql: str) -> PydanticWhereFilterIntersection: # --------------------------------------------------------------------------- -# OSI builders +# Ossie builders # --------------------------------------------------------------------------- diff --git a/converters/dbt/tests/test_msi_to_osi.py b/converters/dbt/tests/test_msi_to_osi.py index 686106bb..a0aa9f8a 100644 --- a/converters/dbt/tests/test_msi_to_osi.py +++ b/converters/dbt/tests/test_msi_to_osi.py @@ -1,13 +1,30 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + import json from typing import List, Optional import pytest from syrupy.assertion import SnapshotAssertion -from osi_dbt.converter_issues import ConverterIssueType -from osi_dbt.filter_utils import _render_filter_template -from osi import OSIDialect, OSIDocument -from osi_dbt.msi_to_osi import MSIToOSIConverter +from ossie_dbt.converter_issues import ConverterIssueType +from ossie_dbt.filter_utils import _render_filter_template +from ossie import OSIDialect, OSIDocument +from ossie_dbt.msi_to_osi import MSIToOSIConverter from metricflow_semantic_interfaces.implementations.metric import ( PydanticConversionTypeParams, PydanticCumulativeTypeParams, @@ -57,7 +74,7 @@ def _field_expr(result: OSIDocument, field_idx: int = 0) -> str: def _osi_metrics(result: OSIDocument) -> list: - """Return OSI metrics for the first semantic model, asserting they exist.""" + """Return Ossie metrics for the first semantic model, asserting they exist.""" metrics = result.semantic_model[0].metrics assert metrics is not None return metrics diff --git a/converters/dbt/tests/test_osi_to_msi.py b/converters/dbt/tests/test_osi_to_msi.py index afb35893..9a2bf86d 100644 --- a/converters/dbt/tests/test_osi_to_msi.py +++ b/converters/dbt/tests/test_osi_to_msi.py @@ -1,10 +1,27 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + """Tests for OSIToMSIConverter.""" import pytest from syrupy.assertion import SnapshotAssertion -from osi_dbt.msi_to_osi import MSIToOSIConverter -from osi_dbt.osi_to_msi import OSIToMSIConverter +from ossie_dbt.msi_to_osi import MSIToOSIConverter +from ossie_dbt.osi_to_msi import OSIToMSIConverter from metricflow_semantic_interfaces.type_enums import ( AggregationType, DimensionType, @@ -331,7 +348,7 @@ def test_percentile_cont_non_median_carries_percentile_param(self) -> None: class TestOSIToMSIRoundTrip: def test_osi_to_msi_to_osi_preserves_structure(self, snapshot: SnapshotAssertion) -> None: - """OSI → MSI → OSI preserves dataset names, fields, and metric expressions.""" + """Ossie → MSI → Ossie preserves dataset names, fields, and metric expressions.""" original = _osi_doc( datasets=[ _osi_dataset( diff --git a/converters/gooddata/README.md b/converters/gooddata/README.md index 318e9e63..8262b1ce 100644 --- a/converters/gooddata/README.md +++ b/converters/gooddata/README.md @@ -1,14 +1,33 @@ -# GoodData OSI Converter + + +# GoodData Apache Ossie Converter Bidirectional converter between GoodData's declarative Logical Data Model (LDM) -and the [OSI (Open Semantic Interchange)](https://github.com/open-semantic-interchange/OSI) +and the [Apache Ossie](https://github.com/apache/ossie) semantic model specification. ## Features -- **GoodData → OSI**: Convert a GoodData declarative LDM JSON to OSI semantic model YAML -- **OSI → GoodData**: Convert an OSI semantic model YAML to GoodData declarative LDM JSON -- Preserves GoodData-specific metadata (labels, date granularities, geo types) via OSI custom_extensions +- **GoodData → Ossie**: Convert a GoodData declarative LDM JSON to Ossie semantic model YAML +- **Ossie → GoodData**: Convert an Ossie semantic model YAML to GoodData declarative LDM JSON +- Preserves GoodData-specific metadata (labels, date granularities, geo types) via Ossie custom_extensions - Generates dual-dialect expressions (ANSI_SQL + MAQL) for fields ## Usage @@ -16,17 +35,17 @@ semantic model specification. ```python import json import yaml -from gooddata_osi import gooddata_to_osi, osi_to_gooddata -from gooddata_osi.models import gd_model_from_dict, gd_model_to_dict +from ossie_gooddata import gooddata_to_osi, osi_to_gooddata +from ossie_gooddata.models import gd_model_from_dict, gd_model_to_dict -# GoodData → OSI +# GoodData → Apache Ossie with open("gooddata_ldm.json") as f: gd_model = gd_model_from_dict(json.load(f)) osi_model = gooddata_to_osi(gd_model, model_name="my_model") with open("osi_model.yaml", "w") as f: yaml.dump(osi_model, f, default_flow_style=False) -# OSI → GoodData +# Apache Ossie → GoodData with open("osi_model.yaml") as f: osi_data = yaml.safe_load(f) gd_model = osi_to_gooddata(osi_data, data_source_id="my_datasource") @@ -43,7 +62,7 @@ uv run pytest ## Concept Mapping -| GoodData LDM | OSI Semantic Model | +| GoodData LDM | Ossie Semantic Model | |---|---| | Dataset | Dataset | | Attribute (+ Labels) | Field with `dimension` metadata | @@ -55,7 +74,7 @@ uv run pytest ## Limitations - **Metrics are not converted.** GoodData metrics use MAQL, a context-aware metric language - where dimensionality and filters are applied at report time. The current OSI metric model + where dimensionality and filters are applied at report time. The current Ossie metric model is SQL-expression-based and cannot represent this paradigm. - AggregatedFacts are not yet supported. - Workspace data filters are not mapped. diff --git a/converters/gooddata/pyproject.toml b/converters/gooddata/pyproject.toml index 7cc6e382..98a56e7b 100644 --- a/converters/gooddata/pyproject.toml +++ b/converters/gooddata/pyproject.toml @@ -1,14 +1,38 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + [project] -name = "gooddata-osi" +name = "apache-ossie-gooddata" version = "0.2.0.dev0" -description = "Bidirectional converter between GoodData LDM and OSI semantic model" +description = "Bidirectional converter between GoodData LDM and Apache Ossie semantic model" authors = [{name = "GoodData Corporation", email = "support@gooddata.com"}] readme = "README.md" requires-python = ">=3.12" +classifiers = [ + "License :: OSI Approved :: Apache Software License", + "Programming Language :: Python :: 3", +] dependencies = [ "pyyaml>=6.0.1", ] +[project.license] +text = "Apache-2.0" + [dependency-groups] dev = [ "ruff>=0.11.5", @@ -21,11 +45,11 @@ requires = ["hatchling"] build-backend = "hatchling.build" [tool.hatch.build] -include = ["src/gooddata_osi/**/*"] +include = ["src/ossie_gooddata/**/*"] ignore-vcs = true [tool.hatch.build.targets.wheel] -packages = ["src/gooddata_osi"] +packages = ["src/ossie_gooddata"] [tool.ruff] line-length = 120 diff --git a/converters/gooddata/src/gooddata_osi/__init__.py b/converters/gooddata/src/gooddata_osi/__init__.py deleted file mode 100644 index a1f5091b..00000000 --- a/converters/gooddata/src/gooddata_osi/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -"""Bidirectional converter between GoodData LDM and OSI semantic model.""" - -from gooddata_osi.gooddata_to_osi import gooddata_to_osi -from gooddata_osi.osi_to_gooddata import osi_to_gooddata - -__all__ = ["gooddata_to_osi", "osi_to_gooddata"] diff --git a/converters/gooddata/src/ossie_gooddata/__init__.py b/converters/gooddata/src/ossie_gooddata/__init__.py new file mode 100644 index 00000000..6074ef57 --- /dev/null +++ b/converters/gooddata/src/ossie_gooddata/__init__.py @@ -0,0 +1,23 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +"""Bidirectional converter between GoodData LDM and Ossie semantic model.""" + +from ossie_gooddata.gooddata_to_osi import gooddata_to_osi +from ossie_gooddata.osi_to_gooddata import osi_to_gooddata + +__all__ = ["gooddata_to_osi", "osi_to_gooddata"] diff --git a/converters/gooddata/src/gooddata_osi/gooddata_to_osi.py b/converters/gooddata/src/ossie_gooddata/gooddata_to_osi.py similarity index 81% rename from converters/gooddata/src/gooddata_osi/gooddata_to_osi.py rename to converters/gooddata/src/ossie_gooddata/gooddata_to_osi.py index 1fc43a5d..db68133d 100644 --- a/converters/gooddata/src/gooddata_osi/gooddata_to_osi.py +++ b/converters/gooddata/src/ossie_gooddata/gooddata_to_osi.py @@ -1,11 +1,28 @@ -"""Convert GoodData declarative LDM to OSI semantic model.""" +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +"""Convert GoodData declarative LDM to Ossie semantic model.""" from __future__ import annotations import json from typing import Any -from gooddata_osi.models import ( +from ossie_gooddata.models import ( GdAttribute, GdDataset, GdDateInstance, @@ -24,22 +41,22 @@ def gooddata_to_osi( model_description: str = "", data_source_id: str | None = None, ) -> dict[str, Any]: - """Convert a GoodData declarative model to an OSI semantic model dict. + """Convert a GoodData declarative model to an Ossie semantic model dict. Args: model: Parsed GoodData declarative model. - model_name: Name for the OSI semantic model. - model_description: Description for the OSI semantic model. + model_name: Name for the Ossie semantic model. + model_description: Description for the Ossie semantic model. data_source_id: Data source ID to include in custom_extensions. Returns: - OSI semantic model as a dict (ready to serialize to YAML). + Ossie semantic model as a dict (ready to serialize to YAML). """ osi_datasets = [] osi_relationships = [] # Map dataset id -> {attribute id -> source column}. Reference targets point - # at a grain attribute id; OSI relationships need the physical column name. + # at a grain attribute id; Ossie relationships need the physical column name. attr_source_col_map: dict[str, dict[str, str]] = {} for ds in model.ldm.datasets: attr_source_col_map[ds.id] = {a.id: a.source_column for a in ds.attributes} @@ -85,7 +102,7 @@ def _convert_dataset( ds: GdDataset, attr_source_col_map: dict[str, dict[str, str]], ) -> tuple[dict[str, Any], list[dict[str, Any]]]: - """Convert a GoodData dataset to an OSI dataset + relationships.""" + """Convert a GoodData dataset to an Ossie dataset + relationships.""" # Build source from dataSourceTableId source = _build_source(ds) @@ -111,7 +128,7 @@ def _convert_dataset( if fields: osi_ds["fields"] = fields - # Convert references to OSI relationships + # Convert references to Ossie relationships relationships = [] for ref in ds.references: rel = _convert_reference(ds.id, ref, attr_source_col_map) @@ -121,7 +138,7 @@ def _convert_dataset( def _convert_date_instance(di: GdDateInstance) -> dict[str, Any]: - """Convert a GoodData date instance to an OSI dataset.""" + """Convert a GoodData date instance to an Ossie dataset.""" osi_ds: dict[str, Any] = { "name": di.id, "source": di.id, @@ -144,7 +161,7 @@ def _convert_date_instance(di: GdDateInstance) -> dict[str, Any]: def _convert_attribute(attr: GdAttribute, dataset_id: str) -> dict[str, Any]: - """Convert a GoodData attribute to an OSI field.""" + """Convert a GoodData attribute to an Ossie field.""" osi_field: dict[str, Any] = { "name": attr.source_column, "expression": { @@ -169,7 +186,7 @@ def _convert_attribute(attr: GdAttribute, dataset_id: str) -> dict[str, Any]: def _convert_fact(fact: GdFact, dataset_id: str) -> dict[str, Any]: - """Convert a GoodData fact to an OSI field.""" + """Convert a GoodData fact to an Ossie field.""" osi_field: dict[str, Any] = { "name": fact.source_column, "expression": { @@ -192,7 +209,7 @@ def _convert_reference( ref: GdReference, attr_source_col_map: dict[str, dict[str, str]], ) -> dict[str, Any]: - """Convert a GoodData reference to an OSI relationship. + """Convert a GoodData reference to an Ossie relationship. Each source carries an explicit `target` grain identifier. For attribute targets the target column is resolved via the target dataset's attribute @@ -232,7 +249,7 @@ def _convert_reference( def _build_source(ds: GdDataset) -> str: - """Build an OSI source string from a GoodData dataset's data source table.""" + """Build an Ossie source string from a GoodData dataset's data source table.""" if ds.data_source_table_id: t = ds.data_source_table_id if t.path: diff --git a/converters/gooddata/src/gooddata_osi/models.py b/converters/gooddata/src/ossie_gooddata/models.py similarity index 93% rename from converters/gooddata/src/gooddata_osi/models.py rename to converters/gooddata/src/ossie_gooddata/models.py index 623981ea..2ef93b04 100644 --- a/converters/gooddata/src/gooddata_osi/models.py +++ b/converters/gooddata/src/ossie_gooddata/models.py @@ -1,4 +1,21 @@ -"""Data models for GoodData declarative LDM and OSI semantic model. +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +"""Data models for GoodData declarative LDM and Ossie semantic model. GoodData LDM structure (from declarative API): DeclarativeModel @@ -18,7 +35,7 @@ ├── granularities[] └── granularitiesFormatting {titleBase, titlePattern} -OSI semantic model structure: +Ossie semantic model structure: version, semantic_model[] ├── name, description, ai_context, custom_extensions[] ├── datasets[] {name, source, primary_key[], fields[], custom_extensions[]} diff --git a/converters/gooddata/src/gooddata_osi/osi_to_gooddata.py b/converters/gooddata/src/ossie_gooddata/osi_to_gooddata.py similarity index 86% rename from converters/gooddata/src/gooddata_osi/osi_to_gooddata.py rename to converters/gooddata/src/ossie_gooddata/osi_to_gooddata.py index 41990f4a..f7f05c89 100644 --- a/converters/gooddata/src/gooddata_osi/osi_to_gooddata.py +++ b/converters/gooddata/src/ossie_gooddata/osi_to_gooddata.py @@ -1,4 +1,21 @@ -"""Convert OSI semantic model to GoodData declarative LDM.""" +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +"""Convert Ossie semantic model to GoodData declarative LDM.""" from __future__ import annotations @@ -6,7 +23,7 @@ import re from typing import Any -from gooddata_osi.models import ( +from ossie_gooddata.models import ( GdAttribute, GdDataset, GdDataSourceTableId, @@ -32,10 +49,10 @@ def osi_to_gooddata( osi_model: dict[str, Any], data_source_id: str = "default", ) -> GdDeclarativeModel: - """Convert an OSI semantic model dict to a GoodData declarative model. + """Convert an Ossie semantic model dict to a GoodData declarative model. Args: - osi_model: Parsed OSI YAML as a dict. + osi_model: Parsed Ossie YAML as a dict. data_source_id: GoodData data source ID to use for table references. Returns: @@ -46,7 +63,7 @@ def osi_to_gooddata( for sm in osi_model.get("semantic_model", []): relationship_map = _build_relationship_map(sm) - # Pre-pass: for each OSI dataset, record whether it is a date instance + # Pre-pass: for each Ossie dataset, record whether it is a date instance # and map its physical source columns to the attribute ids that will # be generated. Reference target columns resolve via this map. target_info = _build_target_info(sm) @@ -103,7 +120,7 @@ def _convert_osi_dataset( target_info: dict[str, dict[str, Any]], data_source_id: str, ) -> tuple[GdDataset, GdDateInstance | None]: - """Convert an OSI dataset to a GoodData dataset or date instance.""" + """Convert an Ossie dataset to a GoodData dataset or date instance.""" ds_name = ds["name"] source = ds.get("source", ds_name) @@ -171,7 +188,7 @@ def _convert_osi_dataset( def _convert_to_date_instance(ds: dict[str, Any], gd_ext: dict[str, Any]) -> GdDateInstance: - """Convert an OSI dataset with date_dimension extension to a GoodData date instance.""" + """Convert an Ossie dataset with date_dimension extension to a GoodData date instance.""" return GdDateInstance( id=ds["name"], title=_get_title(ds), @@ -182,7 +199,7 @@ def _convert_to_date_instance(ds: dict[str, Any], gd_ext: dict[str, Any]) -> GdD def _convert_to_date_instance_from_fields(ds: dict[str, Any]) -> GdDateInstance: - """Convert an all-time-dimension OSI dataset to a GoodData date instance.""" + """Convert an all-time-dimension Ossie dataset to a GoodData date instance.""" return GdDateInstance( id=ds["name"], title=_get_title(ds), @@ -193,7 +210,7 @@ def _convert_to_date_instance_from_fields(ds: dict[str, Any]) -> GdDateInstance: def _convert_to_attribute(field_def: dict[str, Any], dataset_id: str) -> GdAttribute: - """Convert an OSI field (dimension) to a GoodData attribute.""" + """Convert an Ossie field (dimension) to a GoodData attribute.""" field_name = field_def["name"] source_col = _get_source_column(field_def) attr_id = f"attr.{dataset_id}.{field_name}" @@ -224,7 +241,7 @@ def _convert_to_attribute(field_def: dict[str, Any], dataset_id: str) -> GdAttri def _convert_to_fact(field_def: dict[str, Any], dataset_id: str) -> GdFact: - """Convert an OSI field (non-dimension) to a GoodData fact.""" + """Convert an Ossie field (non-dimension) to a GoodData fact.""" field_name = field_def["name"] source_col = _get_source_column(field_def) @@ -237,7 +254,7 @@ def _convert_to_fact(field_def: dict[str, Any], dataset_id: str) -> GdFact: def _get_source_column(field_def: dict[str, Any]) -> str: - """Extract the source column name from an OSI field's ANSI_SQL expression.""" + """Extract the source column name from an Ossie field's ANSI_SQL expression.""" for dialect_expr in field_def.get("expression", {}).get("dialects", []): if dialect_expr.get("dialect") == "ANSI_SQL": return dialect_expr["expression"] @@ -257,7 +274,7 @@ def _detect_type_from_maql(field_def: dict[str, Any]) -> str: def _get_gooddata_extension(obj: dict[str, Any]) -> dict[str, Any] | None: - """Extract the GOODDATA custom extension data from an OSI object.""" + """Extract the GOODDATA custom extension data from an Ossie object.""" for ext in obj.get("custom_extensions", []): if ext.get("vendor_name") == "GOODDATA": data = ext.get("data", "{}") @@ -268,7 +285,7 @@ def _get_gooddata_extension(obj: dict[str, Any]) -> dict[str, Any] | None: def _get_title(obj: dict[str, Any], fallback: str = "") -> str: - """Get a title from an OSI object, preferring ai_context synonyms.""" + """Get a title from an Ossie object, preferring ai_context synonyms.""" ctx = obj.get("ai_context") if isinstance(ctx, dict): synonyms = ctx.get("synonyms", []) @@ -318,7 +335,7 @@ def _is_multivalue(rel: dict[str, Any]) -> bool: def _parse_source_to_table_id(source: str, data_source_id: str) -> GdDataSourceTableId: - """Parse an OSI source string into a GoodData DataSourceTableId.""" + """Parse an Ossie source string into a GoodData DataSourceTableId.""" parts = source.split(".") if len(parts) >= 3: # source_id.schema.table or more diff --git a/converters/gooddata/tests/__init__.py b/converters/gooddata/tests/__init__.py index e69de29b..24569233 100644 --- a/converters/gooddata/tests/__init__.py +++ b/converters/gooddata/tests/__init__.py @@ -0,0 +1,17 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + diff --git a/converters/gooddata/tests/conftest.py b/converters/gooddata/tests/conftest.py index f06cffe1..bb8c8c82 100644 --- a/converters/gooddata/tests/conftest.py +++ b/converters/gooddata/tests/conftest.py @@ -1,4 +1,21 @@ -"""Shared pytest fixtures for gooddata-osi converter tests.""" +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +"""Shared pytest fixtures for Apache Ossie GoodData converter tests.""" from __future__ import annotations @@ -8,7 +25,7 @@ import pytest import yaml -from gooddata_osi.models import GdDeclarativeModel, gd_model_from_dict +from ossie_gooddata.models import GdDeclarativeModel, gd_model_from_dict FIXTURES_DIR = Path(__file__).parent / "fixtures" @@ -28,6 +45,6 @@ def gooddata_tpcds_model(gooddata_tpcds_dict: dict) -> GdDeclarativeModel: @pytest.fixture() def osi_tpcds_dict() -> dict: - """Load the OSI TPC-DS YAML fixture.""" + """Load the Ossie TPC-DS YAML fixture.""" with open(FIXTURES_DIR / "osi_tpcds.yaml") as f: return yaml.safe_load(f) diff --git a/converters/gooddata/tests/fixtures/osi_tpcds.yaml b/converters/gooddata/tests/fixtures/osi_tpcds.yaml index 531a4ddb..af14a2fd 100644 --- a/converters/gooddata/tests/fixtures/osi_tpcds.yaml +++ b/converters/gooddata/tests/fixtures/osi_tpcds.yaml @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + version: "0.2.0.dev0" semantic_model: diff --git a/converters/gooddata/tests/test_gooddata_to_osi.py b/converters/gooddata/tests/test_gooddata_to_osi.py index f4c4ff66..637f77da 100644 --- a/converters/gooddata/tests/test_gooddata_to_osi.py +++ b/converters/gooddata/tests/test_gooddata_to_osi.py @@ -1,13 +1,30 @@ -"""Tests for GoodData → OSI conversion.""" +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +"""Tests for GoodData → Ossie conversion.""" from __future__ import annotations -from gooddata_osi.gooddata_to_osi import gooddata_to_osi -from gooddata_osi.models import GdDeclarativeModel +from ossie_gooddata.gooddata_to_osi import gooddata_to_osi +from ossie_gooddata.models import GdDeclarativeModel def test_basic_conversion(gooddata_tpcds_model: GdDeclarativeModel): - """Verify basic structure of GoodData → OSI conversion.""" + """Verify basic structure of GoodData → Ossie conversion.""" result = gooddata_to_osi(gooddata_tpcds_model, model_name="tpcds_test") assert result["version"] == "0.2.0.dev0" @@ -52,7 +69,7 @@ def test_primary_key_from_grain(gooddata_tpcds_model: GdDeclarativeModel): def test_attributes_become_dimension_fields(gooddata_tpcds_model: GdDeclarativeModel): - """Verify GoodData attributes become OSI fields with dimension metadata.""" + """Verify GoodData attributes become Ossie fields with dimension metadata.""" result = gooddata_to_osi(gooddata_tpcds_model) sm = result["semantic_model"][0] @@ -69,7 +86,7 @@ def test_attributes_become_dimension_fields(gooddata_tpcds_model: GdDeclarativeM def test_facts_become_plain_fields(gooddata_tpcds_model: GdDeclarativeModel): - """Verify GoodData facts become OSI fields without dimension metadata.""" + """Verify GoodData facts become Ossie fields without dimension metadata.""" result = gooddata_to_osi(gooddata_tpcds_model) sm = result["semantic_model"][0] @@ -102,7 +119,7 @@ def test_maql_expressions(gooddata_tpcds_model: GdDeclarativeModel): def test_references_become_relationships(gooddata_tpcds_model: GdDeclarativeModel): - """Verify GoodData references become OSI relationships.""" + """Verify GoodData references become Ossie relationships.""" result = gooddata_to_osi(gooddata_tpcds_model) sm = result["semantic_model"][0] @@ -115,7 +132,7 @@ def test_references_become_relationships(gooddata_tpcds_model: GdDeclarativeMode def test_date_instance_converted(gooddata_tpcds_model: GdDeclarativeModel): - """Verify date instances become OSI datasets with custom_extensions.""" + """Verify date instances become Ossie datasets with custom_extensions.""" result = gooddata_to_osi(gooddata_tpcds_model) sm = result["semantic_model"][0] diff --git a/converters/gooddata/tests/test_models.py b/converters/gooddata/tests/test_models.py index ec2d3eab..64b67d7c 100644 --- a/converters/gooddata/tests/test_models.py +++ b/converters/gooddata/tests/test_models.py @@ -1,8 +1,25 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + """Tests for GoodData declarative model serialization/deserialization.""" from __future__ import annotations -from gooddata_osi.models import GdDeclarativeModel, gd_model_from_dict, gd_model_to_dict +from ossie_gooddata.models import GdDeclarativeModel, gd_model_from_dict, gd_model_to_dict def test_parse_gooddata_model(gooddata_tpcds_dict: dict, gooddata_tpcds_model: GdDeclarativeModel): diff --git a/converters/gooddata/tests/test_osi_to_gooddata.py b/converters/gooddata/tests/test_osi_to_gooddata.py index a112c5c5..abd40914 100644 --- a/converters/gooddata/tests/test_osi_to_gooddata.py +++ b/converters/gooddata/tests/test_osi_to_gooddata.py @@ -1,12 +1,29 @@ -"""Tests for OSI → GoodData conversion.""" +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +"""Tests for Ossie → GoodData conversion.""" from __future__ import annotations -from gooddata_osi.osi_to_gooddata import osi_to_gooddata +from ossie_gooddata.osi_to_gooddata import osi_to_gooddata def test_basic_conversion(osi_tpcds_dict: dict): - """Verify basic structure of OSI → GoodData conversion.""" + """Verify basic structure of Ossie → GoodData conversion.""" result = osi_to_gooddata(osi_tpcds_dict, data_source_id="tpcds") # 4 regular datasets (date_dim becomes a date instance) @@ -25,7 +42,7 @@ def test_dataset_ids(osi_tpcds_dict: dict): def test_date_dimension_detected(osi_tpcds_dict: dict): - """Verify OSI dataset with date_dimension extension becomes a GoodData date instance.""" + """Verify Ossie dataset with date_dimension extension becomes a GoodData date instance.""" result = osi_to_gooddata(osi_tpcds_dict) assert len(result.ldm.date_instances) == 1 @@ -36,7 +53,7 @@ def test_date_dimension_detected(osi_tpcds_dict: dict): def test_dimension_fields_become_attributes(osi_tpcds_dict: dict): - """Verify OSI fields with dimension metadata become GoodData attributes.""" + """Verify Ossie fields with dimension metadata become GoodData attributes.""" result = osi_to_gooddata(osi_tpcds_dict) customer = next(ds for ds in result.ldm.datasets if ds.id == "customer") @@ -46,7 +63,7 @@ def test_dimension_fields_become_attributes(osi_tpcds_dict: dict): def test_non_dimension_fields_become_facts(osi_tpcds_dict: dict): - """Verify OSI fields without dimension become GoodData facts.""" + """Verify Ossie fields without dimension become GoodData facts.""" result = osi_to_gooddata(osi_tpcds_dict) store_sales = next(ds for ds in result.ldm.datasets if ds.id == "store_sales") @@ -76,7 +93,7 @@ def test_grain_from_primary_key(osi_tpcds_dict: dict): def test_relationships_become_references(osi_tpcds_dict: dict): - """Verify OSI relationships become GoodData references.""" + """Verify Ossie relationships become GoodData references.""" result = osi_to_gooddata(osi_tpcds_dict) store_sales = next(ds for ds in result.ldm.datasets if ds.id == "store_sales") diff --git a/converters/gooddata/tests/test_roundtrip.py b/converters/gooddata/tests/test_roundtrip.py index 50544f64..89f4fe08 100644 --- a/converters/gooddata/tests/test_roundtrip.py +++ b/converters/gooddata/tests/test_roundtrip.py @@ -1,19 +1,36 @@ -"""Round-trip conversion tests: GoodData → OSI → GoodData.""" +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +"""Round-trip conversion tests: GoodData → Ossie → GoodData.""" from __future__ import annotations -from gooddata_osi.gooddata_to_osi import gooddata_to_osi -from gooddata_osi.models import GdDeclarativeModel -from gooddata_osi.osi_to_gooddata import osi_to_gooddata +from ossie_gooddata.gooddata_to_osi import gooddata_to_osi +from ossie_gooddata.models import GdDeclarativeModel +from ossie_gooddata.osi_to_gooddata import osi_to_gooddata def test_roundtrip_preserves_datasets(gooddata_tpcds_model: GdDeclarativeModel): - """Verify GoodData → OSI → GoodData preserves dataset count and IDs.""" - # GoodData -> OSI - osi = gooddata_to_osi(gooddata_tpcds_model, model_name="roundtrip_test") + """Verify GoodData → Ossie → GoodData preserves dataset count and IDs.""" + # GoodData -> Ossie + ossie = gooddata_to_osi(gooddata_tpcds_model, model_name="roundtrip_test") - # OSI -> GoodData - result = osi_to_gooddata(osi) + # Ossie -> GoodData + result = osi_to_gooddata(ossie) original_ds_ids = {ds.id for ds in gooddata_tpcds_model.ldm.datasets} result_ds_ids = {ds.id for ds in result.ldm.datasets} @@ -25,8 +42,8 @@ def test_roundtrip_preserves_datasets(gooddata_tpcds_model: GdDeclarativeModel): def test_roundtrip_preserves_date_instances(gooddata_tpcds_model: GdDeclarativeModel): """Verify date instances survive the round trip.""" - osi = gooddata_to_osi(gooddata_tpcds_model) - result = osi_to_gooddata(osi) + ossie = gooddata_to_osi(gooddata_tpcds_model) + result = osi_to_gooddata(ossie) assert len(result.ldm.date_instances) == len(gooddata_tpcds_model.ldm.date_instances) @@ -38,8 +55,8 @@ def test_roundtrip_preserves_date_instances(gooddata_tpcds_model: GdDeclarativeM def test_roundtrip_preserves_references(gooddata_tpcds_model: GdDeclarativeModel): """Verify references/relationships survive the round trip.""" - osi = gooddata_to_osi(gooddata_tpcds_model) - result = osi_to_gooddata(osi) + ossie = gooddata_to_osi(gooddata_tpcds_model) + result = osi_to_gooddata(ossie) original_ss = next(ds for ds in gooddata_tpcds_model.ldm.datasets if ds.id == "store_sales") result_ss = next(ds for ds in result.ldm.datasets if ds.id == "store_sales") @@ -51,8 +68,8 @@ def test_roundtrip_preserves_references(gooddata_tpcds_model: GdDeclarativeModel def test_roundtrip_preserves_attribute_count(gooddata_tpcds_model: GdDeclarativeModel): """Verify attribute counts survive the round trip.""" - osi = gooddata_to_osi(gooddata_tpcds_model) - result = osi_to_gooddata(osi) + ossie = gooddata_to_osi(gooddata_tpcds_model) + result = osi_to_gooddata(ossie) for orig_ds in gooddata_tpcds_model.ldm.datasets: result_ds = next((ds for ds in result.ldm.datasets if ds.id == orig_ds.id), None) @@ -64,8 +81,8 @@ def test_roundtrip_preserves_attribute_count(gooddata_tpcds_model: GdDeclarative def test_roundtrip_preserves_fact_count(gooddata_tpcds_model: GdDeclarativeModel): """Verify fact counts survive the round trip.""" - osi = gooddata_to_osi(gooddata_tpcds_model) - result = osi_to_gooddata(osi) + ossie = gooddata_to_osi(gooddata_tpcds_model) + result = osi_to_gooddata(ossie) for orig_ds in gooddata_tpcds_model.ldm.datasets: result_ds = next((ds for ds in result.ldm.datasets if ds.id == orig_ds.id), None) diff --git a/converters/gooddata/uv.lock b/converters/gooddata/uv.lock index b74fb187..5434a5a9 100644 --- a/converters/gooddata/uv.lock +++ b/converters/gooddata/uv.lock @@ -3,101 +3,8 @@ revision = 3 requires-python = ">=3.12" [[package]] -name = "colorama" -version = "0.4.6" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, -] - -[[package]] -name = "coverage" -version = "7.13.5" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9d/e0/70553e3000e345daff267cec284ce4cbf3fc141b6da229ac52775b5428f1/coverage-7.13.5.tar.gz", hash = "sha256:c81f6515c4c40141f83f502b07bbfa5c240ba25bbe73da7b33f1e5b6120ff179", size = 915967, upload-time = "2026-03-17T10:33:18.341Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/c3/a396306ba7db865bf96fc1fb3b7fd29bcbf3d829df642e77b13555163cd6/coverage-7.13.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:460cf0114c5016fa841214ff5564aa4864f11948da9440bc97e21ad1f4ba1e01", size = 219554, upload-time = "2026-03-17T10:30:42.208Z" }, - { url = "https://files.pythonhosted.org/packages/a6/16/a68a19e5384e93f811dccc51034b1fd0b865841c390e3c931dcc4699e035/coverage-7.13.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0e223ce4b4ed47f065bfb123687686512e37629be25cc63728557ae7db261422", size = 219908, upload-time = "2026-03-17T10:30:43.906Z" }, - { url = "https://files.pythonhosted.org/packages/29/72/20b917c6793af3a5ceb7fb9c50033f3ec7865f2911a1416b34a7cfa0813b/coverage-7.13.5-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6e3370441f4513c6252bf042b9c36d22491142385049243253c7e48398a15a9f", size = 251419, upload-time = "2026-03-17T10:30:45.545Z" }, - { url = "https://files.pythonhosted.org/packages/8c/49/cd14b789536ac6a4778c453c6a2338bc0a2fb60c5a5a41b4008328b9acc1/coverage-7.13.5-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:03ccc709a17a1de074fb1d11f217342fb0d2b1582ed544f554fc9fc3f07e95f5", size = 254159, upload-time = "2026-03-17T10:30:47.204Z" }, - { url = "https://files.pythonhosted.org/packages/9d/00/7b0edcfe64e2ed4c0340dac14a52ad0f4c9bd0b8b5e531af7d55b703db7c/coverage-7.13.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3f4818d065964db3c1c66dc0fbdac5ac692ecbc875555e13374fdbe7eedb4376", size = 255270, upload-time = "2026-03-17T10:30:48.812Z" }, - { url = "https://files.pythonhosted.org/packages/93/89/7ffc4ba0f5d0a55c1e84ea7cee39c9fc06af7b170513d83fbf3bbefce280/coverage-7.13.5-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:012d5319e66e9d5a218834642d6c35d265515a62f01157a45bcc036ecf947256", size = 257538, upload-time = "2026-03-17T10:30:50.77Z" }, - { url = "https://files.pythonhosted.org/packages/81/bd/73ddf85f93f7e6fa83e77ccecb6162d9415c79007b4bc124008a4995e4a7/coverage-7.13.5-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8dd02af98971bdb956363e4827d34425cb3df19ee550ef92855b0acb9c7ce51c", size = 251821, upload-time = "2026-03-17T10:30:52.5Z" }, - { url = "https://files.pythonhosted.org/packages/a0/81/278aff4e8dec4926a0bcb9486320752811f543a3ce5b602cc7a29978d073/coverage-7.13.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f08fd75c50a760c7eb068ae823777268daaf16a80b918fa58eea888f8e3919f5", size = 253191, upload-time = "2026-03-17T10:30:54.543Z" }, - { url = "https://files.pythonhosted.org/packages/70/ee/fe1621488e2e0a58d7e94c4800f0d96f79671553488d401a612bebae324b/coverage-7.13.5-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:843ea8643cf967d1ac7e8ecd4bb00c99135adf4816c0c0593fdcc47b597fcf09", size = 251337, upload-time = "2026-03-17T10:30:56.663Z" }, - { url = "https://files.pythonhosted.org/packages/37/a6/f79fb37aa104b562207cc23cb5711ab6793608e246cae1e93f26b2236ed9/coverage-7.13.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:9d44d7aa963820b1b971dbecd90bfe5fe8f81cff79787eb6cca15750bd2f79b9", size = 255404, upload-time = "2026-03-17T10:30:58.427Z" }, - { url = "https://files.pythonhosted.org/packages/75/f0/ed15262a58ec81ce457ceb717b7f78752a1713556b19081b76e90896e8d4/coverage-7.13.5-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:7132bed4bd7b836200c591410ae7d97bf7ae8be6fc87d160b2bd881df929e7bf", size = 250903, upload-time = "2026-03-17T10:31:00.093Z" }, - { url = "https://files.pythonhosted.org/packages/0f/e9/9129958f20e7e9d4d56d51d42ccf708d15cac355ff4ac6e736e97a9393d2/coverage-7.13.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a698e363641b98843c517817db75373c83254781426e94ada3197cabbc2c919c", size = 252780, upload-time = "2026-03-17T10:31:01.916Z" }, - { url = "https://files.pythonhosted.org/packages/a4/d7/0ad9b15812d81272db94379fe4c6df8fd17781cc7671fdfa30c76ba5ff7b/coverage-7.13.5-cp312-cp312-win32.whl", hash = "sha256:bdba0a6b8812e8c7df002d908a9a2ea3c36e92611b5708633c50869e6d922fdf", size = 222093, upload-time = "2026-03-17T10:31:03.642Z" }, - { url = "https://files.pythonhosted.org/packages/29/3d/821a9a5799fac2556bcf0bd37a70d1d11fa9e49784b6d22e92e8b2f85f18/coverage-7.13.5-cp312-cp312-win_amd64.whl", hash = "sha256:d2c87e0c473a10bffe991502eac389220533024c8082ec1ce849f4218dded810", size = 222900, upload-time = "2026-03-17T10:31:05.651Z" }, - { url = "https://files.pythonhosted.org/packages/d4/fa/2238c2ad08e35cf4f020ea721f717e09ec3152aea75d191a7faf3ef009a8/coverage-7.13.5-cp312-cp312-win_arm64.whl", hash = "sha256:bf69236a9a81bdca3bff53796237aab096cdbf8d78a66ad61e992d9dac7eb2de", size = 221515, upload-time = "2026-03-17T10:31:07.293Z" }, - { url = "https://files.pythonhosted.org/packages/74/8c/74fedc9663dcf168b0a059d4ea756ecae4da77a489048f94b5f512a8d0b3/coverage-7.13.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5ec4af212df513e399cf11610cc27063f1586419e814755ab362e50a85ea69c1", size = 219576, upload-time = "2026-03-17T10:31:09.045Z" }, - { url = "https://files.pythonhosted.org/packages/0c/c9/44fb661c55062f0818a6ffd2685c67aa30816200d5f2817543717d4b92eb/coverage-7.13.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:941617e518602e2d64942c88ec8499f7fbd49d3f6c4327d3a71d43a1973032f3", size = 219942, upload-time = "2026-03-17T10:31:10.708Z" }, - { url = "https://files.pythonhosted.org/packages/5f/13/93419671cee82b780bab7ea96b67c8ef448f5f295f36bf5031154ec9a790/coverage-7.13.5-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:da305e9937617ee95c2e39d8ff9f040e0487cbf1ac174f777ed5eddd7a7c1f26", size = 250935, upload-time = "2026-03-17T10:31:12.392Z" }, - { url = "https://files.pythonhosted.org/packages/ac/68/1666e3a4462f8202d836920114fa7a5ee9275d1fa45366d336c551a162dd/coverage-7.13.5-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:78e696e1cc714e57e8b25760b33a8b1026b7048d270140d25dafe1b0a1ee05a3", size = 253541, upload-time = "2026-03-17T10:31:14.247Z" }, - { url = "https://files.pythonhosted.org/packages/4e/5e/3ee3b835647be646dcf3c65a7c6c18f87c27326a858f72ab22c12730773d/coverage-7.13.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:02ca0eed225b2ff301c474aeeeae27d26e2537942aa0f87491d3e147e784a82b", size = 254780, upload-time = "2026-03-17T10:31:16.193Z" }, - { url = "https://files.pythonhosted.org/packages/44/b3/cb5bd1a04cfcc49ede6cd8409d80bee17661167686741e041abc7ee1b9a9/coverage-7.13.5-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:04690832cbea4e4663d9149e05dba142546ca05cb1848816760e7f58285c970a", size = 256912, upload-time = "2026-03-17T10:31:17.89Z" }, - { url = "https://files.pythonhosted.org/packages/1b/66/c1dceb7b9714473800b075f5c8a84f4588f887a90eb8645282031676e242/coverage-7.13.5-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0590e44dd2745c696a778f7bab6aa95256de2cbc8b8cff4f7db8ff09813d6969", size = 251165, upload-time = "2026-03-17T10:31:19.605Z" }, - { url = "https://files.pythonhosted.org/packages/b7/62/5502b73b97aa2e53ea22a39cf8649ff44827bef76d90bf638777daa27a9d/coverage-7.13.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d7cfad2d6d81dd298ab6b89fe72c3b7b05ec7544bdda3b707ddaecff8d25c161", size = 252908, upload-time = "2026-03-17T10:31:21.312Z" }, - { url = "https://files.pythonhosted.org/packages/7d/37/7792c2d69854397ca77a55c4646e5897c467928b0e27f2d235d83b5d08c6/coverage-7.13.5-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:e092b9499de38ae0fbfbc603a74660eb6ff3e869e507b50d85a13b6db9863e15", size = 250873, upload-time = "2026-03-17T10:31:23.565Z" }, - { url = "https://files.pythonhosted.org/packages/a3/23/bc866fb6163be52a8a9e5d708ba0d3b1283c12158cefca0a8bbb6e247a43/coverage-7.13.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:48c39bc4a04d983a54a705a6389512883d4a3b9862991b3617d547940e9f52b1", size = 255030, upload-time = "2026-03-17T10:31:25.58Z" }, - { url = "https://files.pythonhosted.org/packages/7d/8b/ef67e1c222ef49860701d346b8bbb70881bef283bd5f6cbba68a39a086c7/coverage-7.13.5-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:2d3807015f138ffea1ed9afeeb8624fd781703f2858b62a8dd8da5a0994c57b6", size = 250694, upload-time = "2026-03-17T10:31:27.316Z" }, - { url = "https://files.pythonhosted.org/packages/46/0d/866d1f74f0acddbb906db212e096dee77a8e2158ca5e6bb44729f9d93298/coverage-7.13.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ee2aa19e03161671ec964004fb74b2257805d9710bf14a5c704558b9d8dbaf17", size = 252469, upload-time = "2026-03-17T10:31:29.472Z" }, - { url = "https://files.pythonhosted.org/packages/7a/f5/be742fec31118f02ce42b21c6af187ad6a344fed546b56ca60caacc6a9a0/coverage-7.13.5-cp313-cp313-win32.whl", hash = "sha256:ce1998c0483007608c8382f4ff50164bfc5bd07a2246dd272aa4043b75e61e85", size = 222112, upload-time = "2026-03-17T10:31:31.526Z" }, - { url = "https://files.pythonhosted.org/packages/66/40/7732d648ab9d069a46e686043241f01206348e2bbf128daea85be4d6414b/coverage-7.13.5-cp313-cp313-win_amd64.whl", hash = "sha256:631efb83f01569670a5e866ceb80fe483e7c159fac6f167e6571522636104a0b", size = 222923, upload-time = "2026-03-17T10:31:33.633Z" }, - { url = "https://files.pythonhosted.org/packages/48/af/fea819c12a095781f6ccd504890aaddaf88b8fab263c4940e82c7b770124/coverage-7.13.5-cp313-cp313-win_arm64.whl", hash = "sha256:f4cd16206ad171cbc2470dbea9103cf9a7607d5fe8c242fdf1edf36174020664", size = 221540, upload-time = "2026-03-17T10:31:35.445Z" }, - { url = "https://files.pythonhosted.org/packages/23/d2/17879af479df7fbbd44bd528a31692a48f6b25055d16482fdf5cdb633805/coverage-7.13.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0428cbef5783ad91fe240f673cc1f76b25e74bbfe1a13115e4aa30d3f538162d", size = 220262, upload-time = "2026-03-17T10:31:37.184Z" }, - { url = "https://files.pythonhosted.org/packages/5b/4c/d20e554f988c8f91d6a02c5118f9abbbf73a8768a3048cb4962230d5743f/coverage-7.13.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e0b216a19534b2427cc201a26c25da4a48633f29a487c61258643e89d28200c0", size = 220617, upload-time = "2026-03-17T10:31:39.245Z" }, - { url = "https://files.pythonhosted.org/packages/29/9c/f9f5277b95184f764b24e7231e166dfdb5780a46d408a2ac665969416d61/coverage-7.13.5-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:972a9cd27894afe4bc2b1480107054e062df08e671df7c2f18c205e805ccd806", size = 261912, upload-time = "2026-03-17T10:31:41.324Z" }, - { url = "https://files.pythonhosted.org/packages/d5/f6/7f1ab39393eeb50cfe4747ae8ef0e4fc564b989225aa1152e13a180d74f8/coverage-7.13.5-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4b59148601efcd2bac8c4dbf1f0ad6391693ccf7a74b8205781751637076aee3", size = 263987, upload-time = "2026-03-17T10:31:43.724Z" }, - { url = "https://files.pythonhosted.org/packages/a0/d7/62c084fb489ed9c6fbdf57e006752e7c516ea46fd690e5ed8b8617c7d52e/coverage-7.13.5-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:505d7083c8b0c87a8fa8c07370c285847c1f77739b22e299ad75a6af6c32c5c9", size = 266416, upload-time = "2026-03-17T10:31:45.769Z" }, - { url = "https://files.pythonhosted.org/packages/a9/f6/df63d8660e1a0bff6125947afda112a0502736f470d62ca68b288ea762d8/coverage-7.13.5-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:60365289c3741e4db327e7baff2a4aaacf22f788e80fa4683393891b70a89fbd", size = 267558, upload-time = "2026-03-17T10:31:48.293Z" }, - { url = "https://files.pythonhosted.org/packages/5b/02/353ca81d36779bd108f6d384425f7139ac3c58c750dcfaafe5d0bee6436b/coverage-7.13.5-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1b88c69c8ef5d4b6fe7dea66d6636056a0f6a7527c440e890cf9259011f5e606", size = 261163, upload-time = "2026-03-17T10:31:50.125Z" }, - { url = "https://files.pythonhosted.org/packages/2c/16/2e79106d5749bcaf3aee6d309123548e3276517cd7851faa8da213bc61bf/coverage-7.13.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5b13955d31d1633cf9376908089b7cebe7d15ddad7aeaabcbe969a595a97e95e", size = 263981, upload-time = "2026-03-17T10:31:51.961Z" }, - { url = "https://files.pythonhosted.org/packages/29/c7/c29e0c59ffa6942030ae6f50b88ae49988e7e8da06de7ecdbf49c6d4feae/coverage-7.13.5-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:f70c9ab2595c56f81a89620e22899eea8b212a4041bd728ac6f4a28bf5d3ddd0", size = 261604, upload-time = "2026-03-17T10:31:53.872Z" }, - { url = "https://files.pythonhosted.org/packages/40/48/097cdc3db342f34006a308ab41c3a7c11c3f0d84750d340f45d88a782e00/coverage-7.13.5-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:084b84a8c63e8d6fc7e3931b316a9bcafca1458d753c539db82d31ed20091a87", size = 265321, upload-time = "2026-03-17T10:31:55.997Z" }, - { url = "https://files.pythonhosted.org/packages/bb/1f/4994af354689e14fd03a75f8ec85a9a68d94e0188bbdab3fc1516b55e512/coverage-7.13.5-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ad14385487393e386e2ea988b09d62dd42c397662ac2dabc3832d71253eee479", size = 260502, upload-time = "2026-03-17T10:31:58.308Z" }, - { url = "https://files.pythonhosted.org/packages/22/c6/9bb9ef55903e628033560885f5c31aa227e46878118b63ab15dc7ba87797/coverage-7.13.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:7f2c47b36fe7709a6e83bfadf4eefb90bd25fbe4014d715224c4316f808e59a2", size = 262688, upload-time = "2026-03-17T10:32:00.141Z" }, - { url = "https://files.pythonhosted.org/packages/14/4f/f5df9007e50b15e53e01edea486814783a7f019893733d9e4d6caad75557/coverage-7.13.5-cp313-cp313t-win32.whl", hash = "sha256:67e9bc5449801fad0e5dff329499fb090ba4c5800b86805c80617b4e29809b2a", size = 222788, upload-time = "2026-03-17T10:32:02.246Z" }, - { url = "https://files.pythonhosted.org/packages/e1/98/aa7fccaa97d0f3192bec013c4e6fd6d294a6ed44b640e6bb61f479e00ed5/coverage-7.13.5-cp313-cp313t-win_amd64.whl", hash = "sha256:da86cdcf10d2519e10cabb8ac2de03da1bcb6e4853790b7fbd48523332e3a819", size = 223851, upload-time = "2026-03-17T10:32:04.416Z" }, - { url = "https://files.pythonhosted.org/packages/3d/8b/e5c469f7352651e5f013198e9e21f97510b23de957dd06a84071683b4b60/coverage-7.13.5-cp313-cp313t-win_arm64.whl", hash = "sha256:0ecf12ecb326fe2c339d93fc131816f3a7367d223db37817208905c89bded911", size = 222104, upload-time = "2026-03-17T10:32:06.65Z" }, - { url = "https://files.pythonhosted.org/packages/8e/77/39703f0d1d4b478bfd30191d3c14f53caf596fac00efb3f8f6ee23646439/coverage-7.13.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fbabfaceaeb587e16f7008f7795cd80d20ec548dc7f94fbb0d4ec2e038ce563f", size = 219621, upload-time = "2026-03-17T10:32:08.589Z" }, - { url = "https://files.pythonhosted.org/packages/e2/3e/51dff36d99ae14639a133d9b164d63e628532e2974d8b1edb99dd1ebc733/coverage-7.13.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9bb2a28101a443669a423b665939381084412b81c3f8c0fcfbac57f4e30b5b8e", size = 219953, upload-time = "2026-03-17T10:32:10.507Z" }, - { url = "https://files.pythonhosted.org/packages/6a/6c/1f1917b01eb647c2f2adc9962bd66c79eb978951cab61bdc1acab3290c07/coverage-7.13.5-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bd3a2fbc1c6cccb3c5106140d87cc6a8715110373ef42b63cf5aea29df8c217a", size = 250992, upload-time = "2026-03-17T10:32:12.41Z" }, - { url = "https://files.pythonhosted.org/packages/22/e5/06b1f88f42a5a99df42ce61208bdec3bddb3d261412874280a19796fc09c/coverage-7.13.5-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6c36ddb64ed9d7e496028d1d00dfec3e428e0aabf4006583bb1839958d280510", size = 253503, upload-time = "2026-03-17T10:32:14.449Z" }, - { url = "https://files.pythonhosted.org/packages/80/28/2a148a51e5907e504fa7b85490277734e6771d8844ebcc48764a15e28155/coverage-7.13.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:380e8e9084d8eb38db3a9176a1a4f3c0082c3806fa0dc882d1d87abc3c789247", size = 254852, upload-time = "2026-03-17T10:32:16.56Z" }, - { url = "https://files.pythonhosted.org/packages/61/77/50e8d3d85cc0b7ebe09f30f151d670e302c7ff4a1bf6243f71dd8b0981fa/coverage-7.13.5-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e808af52a0513762df4d945ea164a24b37f2f518cbe97e03deaa0ee66139b4d6", size = 257161, upload-time = "2026-03-17T10:32:19.004Z" }, - { url = "https://files.pythonhosted.org/packages/3b/c4/b5fd1d4b7bf8d0e75d997afd3925c59ba629fc8616f1b3aae7605132e256/coverage-7.13.5-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e301d30dd7e95ae068671d746ba8c34e945a82682e62918e41b2679acd2051a0", size = 251021, upload-time = "2026-03-17T10:32:21.344Z" }, - { url = "https://files.pythonhosted.org/packages/f8/66/6ea21f910e92d69ef0b1c3346ea5922a51bad4446c9126db2ae96ee24c4c/coverage-7.13.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:800bc829053c80d240a687ceeb927a94fd108bbdc68dfbe505d0d75ab578a882", size = 252858, upload-time = "2026-03-17T10:32:23.506Z" }, - { url = "https://files.pythonhosted.org/packages/9e/ea/879c83cb5d61aa2a35fb80e72715e92672daef8191b84911a643f533840c/coverage-7.13.5-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:0b67af5492adb31940ee418a5a655c28e48165da5afab8c7fa6fd72a142f8740", size = 250823, upload-time = "2026-03-17T10:32:25.516Z" }, - { url = "https://files.pythonhosted.org/packages/8a/fb/616d95d3adb88b9803b275580bdeee8bd1b69a886d057652521f83d7322f/coverage-7.13.5-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:c9136ff29c3a91e25b1d1552b5308e53a1e0653a23e53b6366d7c2dcbbaf8a16", size = 255099, upload-time = "2026-03-17T10:32:27.944Z" }, - { url = "https://files.pythonhosted.org/packages/1c/93/25e6917c90ec1c9a56b0b26f6cad6408e5f13bb6b35d484a0d75c9cf000d/coverage-7.13.5-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:cff784eef7f0b8f6cb28804fbddcfa99f89efe4cc35fb5627e3ac58f91ed3ac0", size = 250638, upload-time = "2026-03-17T10:32:29.914Z" }, - { url = "https://files.pythonhosted.org/packages/fc/7b/dc1776b0464145a929deed214aef9fb1493f159b59ff3c7eeeedf91eddd0/coverage-7.13.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:68a4953be99b17ac3c23b6efbc8a38330d99680c9458927491d18700ef23ded0", size = 252295, upload-time = "2026-03-17T10:32:31.981Z" }, - { url = "https://files.pythonhosted.org/packages/ea/fb/99cbbc56a26e07762a2740713f3c8f9f3f3106e3a3dd8cc4474954bccd34/coverage-7.13.5-cp314-cp314-win32.whl", hash = "sha256:35a31f2b1578185fbe6aa2e74cea1b1d0bbf4c552774247d9160d29b80ed56cc", size = 222360, upload-time = "2026-03-17T10:32:34.233Z" }, - { url = "https://files.pythonhosted.org/packages/8d/b7/4758d4f73fb536347cc5e4ad63662f9d60ba9118cb6785e9616b2ce5d7fa/coverage-7.13.5-cp314-cp314-win_amd64.whl", hash = "sha256:2aa055ae1857258f9e0045be26a6d62bdb47a72448b62d7b55f4820f361a2633", size = 223174, upload-time = "2026-03-17T10:32:36.369Z" }, - { url = "https://files.pythonhosted.org/packages/2c/f2/24d84e1dfe70f8ac9fdf30d338239860d0d1d5da0bda528959d0ebc9da28/coverage-7.13.5-cp314-cp314-win_arm64.whl", hash = "sha256:1b11eef33edeae9d142f9b4358edb76273b3bfd30bc3df9a4f95d0e49caf94e8", size = 221739, upload-time = "2026-03-17T10:32:38.736Z" }, - { url = "https://files.pythonhosted.org/packages/60/5b/4a168591057b3668c2428bff25dd3ebc21b629d666d90bcdfa0217940e84/coverage-7.13.5-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:10a0c37f0b646eaff7cce1874c31d1f1ccb297688d4c747291f4f4c70741cc8b", size = 220351, upload-time = "2026-03-17T10:32:41.196Z" }, - { url = "https://files.pythonhosted.org/packages/f5/21/1fd5c4dbfe4a58b6b99649125635df46decdfd4a784c3cd6d410d303e370/coverage-7.13.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b5db73ba3c41c7008037fa731ad5459fc3944cb7452fc0aa9f822ad3533c583c", size = 220612, upload-time = "2026-03-17T10:32:43.204Z" }, - { url = "https://files.pythonhosted.org/packages/d6/fe/2a924b3055a5e7e4512655a9d4609781b0d62334fa0140c3e742926834e2/coverage-7.13.5-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:750db93a81e3e5a9831b534be7b1229df848b2e125a604fe6651e48aa070e5f9", size = 261985, upload-time = "2026-03-17T10:32:45.514Z" }, - { url = "https://files.pythonhosted.org/packages/d7/0d/c8928f2bd518c45990fe1a2ab8db42e914ef9b726c975facc4282578c3eb/coverage-7.13.5-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9ddb4f4a5479f2539644be484da179b653273bca1a323947d48ab107b3ed1f29", size = 264107, upload-time = "2026-03-17T10:32:47.971Z" }, - { url = "https://files.pythonhosted.org/packages/ef/ae/4ae35bbd9a0af9d820362751f0766582833c211224b38665c0f8de3d487f/coverage-7.13.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8a7a2049c14f413163e2bdabd37e41179b1d1ccb10ffc6ccc4b7a718429c607", size = 266513, upload-time = "2026-03-17T10:32:50.1Z" }, - { url = "https://files.pythonhosted.org/packages/9c/20/d326174c55af36f74eac6ae781612d9492f060ce8244b570bb9d50d9d609/coverage-7.13.5-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1c85e0b6c05c592ea6d8768a66a254bfb3874b53774b12d4c89c481eb78cb90", size = 267650, upload-time = "2026-03-17T10:32:52.391Z" }, - { url = "https://files.pythonhosted.org/packages/7a/5e/31484d62cbd0eabd3412e30d74386ece4a0837d4f6c3040a653878bfc019/coverage-7.13.5-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:777c4d1eff1b67876139d24288aaf1817f6c03d6bae9c5cc8d27b83bcfe38fe3", size = 261089, upload-time = "2026-03-17T10:32:54.544Z" }, - { url = "https://files.pythonhosted.org/packages/e9/d8/49a72d6de146eebb0b7e48cc0f4bc2c0dd858e3d4790ab2b39a2872b62bd/coverage-7.13.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:6697e29b93707167687543480a40f0db8f356e86d9f67ddf2e37e2dfd91a9dab", size = 263982, upload-time = "2026-03-17T10:32:56.803Z" }, - { url = "https://files.pythonhosted.org/packages/06/3b/0351f1bd566e6e4dd39e978efe7958bde1d32f879e85589de147654f57bb/coverage-7.13.5-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:8fdf453a942c3e4d99bd80088141c4c6960bb232c409d9c3558e2dbaa3998562", size = 261579, upload-time = "2026-03-17T10:32:59.466Z" }, - { url = "https://files.pythonhosted.org/packages/5d/ce/796a2a2f4017f554d7810f5c573449b35b1e46788424a548d4d19201b222/coverage-7.13.5-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:32ca0c0114c9834a43f045a87dcebd69d108d8ffb666957ea65aa132f50332e2", size = 265316, upload-time = "2026-03-17T10:33:01.847Z" }, - { url = "https://files.pythonhosted.org/packages/3d/16/d5ae91455541d1a78bc90abf495be600588aff8f6db5c8b0dae739fa39c9/coverage-7.13.5-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:8769751c10f339021e2638cd354e13adeac54004d1941119b2c96fe5276d45ea", size = 260427, upload-time = "2026-03-17T10:33:03.945Z" }, - { url = "https://files.pythonhosted.org/packages/48/11/07f413dba62db21fb3fad5d0de013a50e073cc4e2dc4306e770360f6dfc8/coverage-7.13.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cec2d83125531bd153175354055cdb7a09987af08a9430bd173c937c6d0fba2a", size = 262745, upload-time = "2026-03-17T10:33:06.285Z" }, - { url = "https://files.pythonhosted.org/packages/91/15/d792371332eb4663115becf4bad47e047d16234b1aff687b1b18c58d60ae/coverage-7.13.5-cp314-cp314t-win32.whl", hash = "sha256:0cd9ed7a8b181775459296e402ca4fb27db1279740a24e93b3b41942ebe4b215", size = 223146, upload-time = "2026-03-17T10:33:08.756Z" }, - { url = "https://files.pythonhosted.org/packages/db/51/37221f59a111dca5e85be7dbf09696323b5b9f13ff65e0641d535ed06ea8/coverage-7.13.5-cp314-cp314t-win_amd64.whl", hash = "sha256:301e3b7dfefecaca37c9f1aa6f0049b7d4ab8dd933742b607765d757aca77d43", size = 224254, upload-time = "2026-03-17T10:33:11.174Z" }, - { url = "https://files.pythonhosted.org/packages/54/83/6acacc889de8987441aa7d5adfbdbf33d288dad28704a67e574f1df9bcbb/coverage-7.13.5-cp314-cp314t-win_arm64.whl", hash = "sha256:9dacc2ad679b292709e0f5fc1ac74a6d4d5562e424058962c7bb0c658ad25e45", size = 222276, upload-time = "2026-03-17T10:33:13.466Z" }, - { url = "https://files.pythonhosted.org/packages/9e/ee/a4cf96b8ce1e566ed238f0659ac2d3f007ed1d14b181bcb684e19561a69a/coverage-7.13.5-py3-none-any.whl", hash = "sha256:34b02417cf070e173989b3db962f7ed56d2f644307b2cf9d5a0f258e13084a61", size = 211346, upload-time = "2026-03-17T10:33:15.691Z" }, -] - -[[package]] -name = "gooddata-osi" -version = "0.1.0" +name = "apache-ossie-gooddata" +version = "0.2.0.dev0" source = { editable = "." } dependencies = [ { name = "pyyaml" }, @@ -120,6 +27,99 @@ dev = [ { name = "ruff", specifier = ">=0.11.5" }, ] +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "coverage" +version = "7.14.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/fd/0ab2772530e946e1be1abd0bc09e647ec9b02e88f0867857601fefca8953/coverage-7.14.1.tar.gz", hash = "sha256:30c08f7d90415aa98b3c990385dea2939b0da55f38515e5b369b83655f8523be", size = 920132, upload-time = "2026-05-26T20:41:36.783Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3d/b7/bdbb725ba02c5b42825b200c940f38b7a54fcad24627b7192f78f8110d76/coverage-7.14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a06c76364a9360e33d6d23769aefdf7f66f38e2ffb60ceb1baaa4989d83b695c", size = 220022, upload-time = "2026-05-26T20:39:03.702Z" }, + { url = "https://files.pythonhosted.org/packages/72/81/fdc0898a55c6219223291ec1a1fe89966ef212ce82276aa0899df84b5de0/coverage-7.14.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fad54e871165f6ec2f536063ac74c3104508a12963e64072ba44bd822de52b0c", size = 220379, upload-time = "2026-05-26T20:39:05.381Z" }, + { url = "https://files.pythonhosted.org/packages/de/72/de048c4a25e13bce59ac6a339351c10bdf2515e07459afcdaf04dc3143a2/coverage-7.14.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:84b535f00655ecafe1d929d1fb00ed5d6fa3051ea643ab2c161a3887b86f294b", size = 251888, upload-time = "2026-05-26T20:39:07.367Z" }, + { url = "https://files.pythonhosted.org/packages/28/30/300c343f68beb9d4cbb64ec81e58c5b6b80b56927f72d2b38654ac26e013/coverage-7.14.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6b6b0853b895fe0e98cbfc580d1ec3393d9302b4b1e96a77b3f5c91fdab899e6", size = 254624, upload-time = "2026-05-26T20:39:09.037Z" }, + { url = "https://files.pythonhosted.org/packages/b1/ed/7b25642496e8170b6bac14adce00537c6e5fa2d586159401a4de3e8b49e6/coverage-7.14.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:442cc9c952b2df400cda54bb04ab87330cf2cd08a8692cbbea36773531eb6f37", size = 255739, upload-time = "2026-05-26T20:39:10.889Z" }, + { url = "https://files.pythonhosted.org/packages/7f/a2/abd210b8c4e29c24e4624916db97bb519097a91034aaeb767f937e7da794/coverage-7.14.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8270544c361ed405a27a060dbc9ed2c124b084d96dfdc2d9a2510482aef981ad", size = 257998, upload-time = "2026-05-26T20:39:12.722Z" }, + { url = "https://files.pythonhosted.org/packages/7f/24/7c50beed3792fe62f6ce0545c6686ce83379719e2c0276179333d97eae92/coverage-7.14.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:48b283b1dd6372e8de2a7a9a4c4d5dc06f4d4fd209b876f3c88a7a205a0c8f84", size = 252296, upload-time = "2026-05-26T20:39:14.259Z" }, + { url = "https://files.pythonhosted.org/packages/15/05/0f874628ebcbfc77ead559ff210281ef06a97db08481832e7dd39274a135/coverage-7.14.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5b0c99ba93a07d56f6df340bb79be53202a082b2fdb81bfe6190b741a3470d54", size = 253658, upload-time = "2026-05-26T20:39:15.923Z" }, + { url = "https://files.pythonhosted.org/packages/99/6f/ca6ad067364b337ef997802115e7ecad2abd2248b05471464b0dea02b4d4/coverage-7.14.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e471bc5769ff073b058cfadb0d736b56ce067c8560eabeb0da88462df98c23e7", size = 251803, upload-time = "2026-05-26T20:39:17.537Z" }, + { url = "https://files.pythonhosted.org/packages/c0/30/b9b4d377cd9f40baf228068f5a81faf8450c6228503011bd499708483a50/coverage-7.14.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f497a1ea81d4cd7c10ddcaa685135b9aabd291af3d55775a9ddf3cb7a364cdd9", size = 255873, upload-time = "2026-05-26T20:39:19.414Z" }, + { url = "https://files.pythonhosted.org/packages/3c/21/7c721a9e5e6bb88547d30a787aefb97512d3f54c1324c7488d9b3743f7f9/coverage-7.14.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:2222be86d0b54f5dd5a38f45f17f315f737245e857bf0bdedc70734f84a13c02", size = 251372, upload-time = "2026-05-26T20:39:21.169Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f8ae5a2200130e1503cd7661a6cd3b2b7bacef98277fbf3571fb13f8b766/coverage-7.14.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:85e85586565842f6932abebd4c18bcb1074223dc0b3576e7d173ca710622813a", size = 253245, upload-time = "2026-05-26T20:39:23.097Z" }, + { url = "https://files.pythonhosted.org/packages/34/62/70a9024672a5f6910517d9628c52c9afbdd3cf8f46426af52bb148a56fff/coverage-7.14.1-cp312-cp312-win32.whl", hash = "sha256:4a28fd227808366b196a75476dced2eb35b351d6766ba9c858dc93319e87f4f1", size = 222567, upload-time = "2026-05-26T20:39:24.868Z" }, + { url = "https://files.pythonhosted.org/packages/f6/81/8b7cd386839b039ebe1855733b9f9449a8dec5d79564018234f185a7fa70/coverage-7.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:54acdb6674a4661768d7bf7db32dfb9f46ab1d764f8aba6df75ce1a6a088724e", size = 223372, upload-time = "2026-05-26T20:39:26.603Z" }, + { url = "https://files.pythonhosted.org/packages/ae/ba/b44d472022f620d289d95fa830143235c0c36461c6f2437ea8d51e5481ed/coverage-7.14.1-cp312-cp312-win_arm64.whl", hash = "sha256:99cd41ff91afd94896fea3bc002706b6ae4ce95727d06e4a0f39c0a8d8bd8b1a", size = 221989, upload-time = "2026-05-26T20:39:28.242Z" }, + { url = "https://files.pythonhosted.org/packages/8a/9e/5f6d56327c62b185225d145191c607e07515294a0aa6338e58805cd4a5ac/coverage-7.14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:be9f2c802dcfce3f71298303aa5dad0dce440a76c52f2f60dacd8656dab78793", size = 220044, upload-time = "2026-05-26T20:39:29.902Z" }, + { url = "https://files.pythonhosted.org/packages/75/92/e82aca356744cbbc0f77a0b623e38918c1872361963413a3bab5d0340393/coverage-7.14.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6223a72fd0e4c7156353ec0f08a5f93623e1d3034d0e2683b9bb8ea674131b1d", size = 220412, upload-time = "2026-05-26T20:39:31.561Z" }, + { url = "https://files.pythonhosted.org/packages/27/c9/385bde0bf7ed0f4bf3a7ee5367060a86b5d218718cfd6fb943c0f836b34f/coverage-7.14.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7279d2110a28cebc738b6459ecda2771735a4c18465fbbd36b3288fe5ed92247", size = 251412, upload-time = "2026-05-26T20:39:33.337Z" }, + { url = "https://files.pythonhosted.org/packages/51/8c/23faf6a2343a0d17f960a4bd56c43bc7eb4cf312f774dd6ceebd82c7d8fc/coverage-7.14.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9eeb3fcbc13ba40dfbdb22d01d196a28e9cef9ed4c29b60061a1e0e823a9929d", size = 254008, upload-time = "2026-05-26T20:39:35.009Z" }, + { url = "https://files.pythonhosted.org/packages/42/06/36f4aa9ca8a815e6036156e80706a67828bb97bd826948244f6996dda957/coverage-7.14.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f0cfc27c539f07cf5c0a4cfe211d0b6cae039f8f40526dbaa71944e64b50a7b", size = 255241, upload-time = "2026-05-26T20:39:36.71Z" }, + { url = "https://files.pythonhosted.org/packages/ca/79/95266316352f90f6b1c6736bb413302edfde2453fb32422d3911642691b3/coverage-7.14.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:221c70f316241a78e77e607c227cefc8808d4e08f28d99c04f35694690e940be", size = 257373, upload-time = "2026-05-26T20:39:38.412Z" }, + { url = "https://files.pythonhosted.org/packages/e3/9c/58316d1f66c488b5fca8a0eb3e98348807813efa8a0d0833b9021be27488/coverage-7.14.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:da028256b04ec30e5e0114b6f76172938c313991f0a2d3d894271315cf5d5e43", size = 251635, upload-time = "2026-05-26T20:39:40.268Z" }, + { url = "https://files.pythonhosted.org/packages/ef/5a/ca2398a568e16fed7bb713e84ba3603a7164fb65779abe645c565ec890d5/coverage-7.14.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:76a085d7005236a767e3426148b2c407e53ad61695c562f8a81da2d373324901", size = 253373, upload-time = "2026-05-26T20:39:42.145Z" }, + { url = "https://files.pythonhosted.org/packages/6e/2c/0396562c32deaebe7be51d865b3a41e9a87d7561acafe1a28f53b07e019a/coverage-7.14.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b553d04b5e778a8e56d57eb134aff42a92718ecba45e79c4764ecfa40efd92ff", size = 251341, upload-time = "2026-05-26T20:39:43.907Z" }, + { url = "https://files.pythonhosted.org/packages/fd/8f/a94f9221184c9cae1ee115820e3798e48b6b17777a9f19e46fb9a0c8dc74/coverage-7.14.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:46f714d2fb8ae2f4f29f23ada7f1e79b759fff5a70f94a1dac23af204c3ec9e4", size = 255497, upload-time = "2026-05-26T20:39:46.166Z" }, + { url = "https://files.pythonhosted.org/packages/71/69/505d70e47db1eaebcd002c39759707621ef184cd6b1ae084d9f41293f323/coverage-7.14.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:1896f5e19ff3f0431c7ce2172adc54890fd97f86b59ced8ca1649145d9ffe35d", size = 251159, upload-time = "2026-05-26T20:39:48.03Z" }, + { url = "https://files.pythonhosted.org/packages/e0/aa/58681c383aa33a9d2ed40a02d7a22fbf780d1fa4d575396365777828198c/coverage-7.14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:62fd185ef9df3c33d1c8178c5af105f762afbad96038de9a4ae100aa6297ca33", size = 252934, upload-time = "2026-05-26T20:39:49.872Z" }, + { url = "https://files.pythonhosted.org/packages/eb/fd/11c928cd6bdffc7074bb5965c173d9ebf517fb00205e1da524b98d29ef92/coverage-7.14.1-cp313-cp313-win32.whl", hash = "sha256:ab4af6352741a604c431c6072fce5bee33bf0f20dc7a56618d6bf6bb89e9810c", size = 222584, upload-time = "2026-05-26T20:39:51.68Z" }, + { url = "https://files.pythonhosted.org/packages/6f/92/fb416fc26d340dcba19518c418d6048e913186e17243982c5e435e41fa7a/coverage-7.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:7af486dabe8954d03b087f0021540897afe084f04e16ff5579e08cc46f871416", size = 223394, upload-time = "2026-05-26T20:39:53.472Z" }, + { url = "https://files.pythonhosted.org/packages/73/c6/02d56e3867972f77d5036de924643f26c056e848f00452cafb4dbc3c29b4/coverage-7.14.1-cp313-cp313-win_arm64.whl", hash = "sha256:2224f89ffd0c5605ccce1ed7a584da162bc7c55f601ab1c946bc9de31a486b42", size = 222015, upload-time = "2026-05-26T20:39:55.374Z" }, + { url = "https://files.pythonhosted.org/packages/4d/9e/fcc77914050df73f7662fa1f00902774c79c075a8388ab334074574bf77e/coverage-7.14.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:de286598cc65d2b489411174b1faec2f5a7775fb3201fd925db2a76b4030f37d", size = 220733, upload-time = "2026-05-26T20:39:57.189Z" }, + { url = "https://files.pythonhosted.org/packages/f7/67/2963cbdaf5cbadec44efa3a1e39eaa1f02df4079585f05387607a221e126/coverage-7.14.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:042c46ded7c288aeb07cf14a28b6c1e10b78fcba40171c3fa1e939377eeef0b5", size = 221086, upload-time = "2026-05-26T20:39:59.019Z" }, + { url = "https://files.pythonhosted.org/packages/c8/c5/8701645574e11881f2f47d8930f98bc48b5d43b25eb5b4430dfc4a2f9f48/coverage-7.14.1-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f4ddbe407477f04c45115d1a4e5bc480f753553b534d338d4c3358b1cdd0ea52", size = 262381, upload-time = "2026-05-26T20:40:00.822Z" }, + { url = "https://files.pythonhosted.org/packages/7c/28/7a64d73598263e0c5abd5084211a8474488d31b3c552ff531c719dfcff62/coverage-7.14.1-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d13e6725992e2d2fd7d81d4f5241952d13740121dfd501da09201be39b2c003a", size = 264458, upload-time = "2026-05-26T20:40:02.506Z" }, + { url = "https://files.pythonhosted.org/packages/fa/d8/4969179db9f7eb4df218e69540adf829d1c835f59452513d065d15446802/coverage-7.14.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f747dc8edcfe740130f28f32f3995e955494285717e86ee25af51db2219df08a", size = 266884, upload-time = "2026-05-26T20:40:04.421Z" }, + { url = "https://files.pythonhosted.org/packages/a6/78/a45d5794dbc9bafd97afc96a4377c86c7820d78b6cf51b89bc1d4e919275/coverage-7.14.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ced2f09ef276fd58611a1ef502164ad266d2b75174e5a40cabbdb4033f9f6cf2", size = 268022, upload-time = "2026-05-26T20:40:06.298Z" }, + { url = "https://files.pythonhosted.org/packages/21/cb/4f5e354e9e3e67af96bd4e57113e6db6b22298c7168b13eec408a549903d/coverage-7.14.1-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b84800013769a78ccb9ef4659402e26d06867e337b61ec365f77ad008adea80e", size = 261631, upload-time = "2026-05-26T20:40:08.226Z" }, + { url = "https://files.pythonhosted.org/packages/ec/49/eced49af4cb996d5d8b7e94e736175c513e4facd3398507b89892b4326d8/coverage-7.14.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:ea8cd6ca0ee9f616aaef3afc6882e32c2cbf18b00d96313ffd76af650574034d", size = 264443, upload-time = "2026-05-26T20:40:10.137Z" }, + { url = "https://files.pythonhosted.org/packages/f1/d8/5603a88a7c5913a6b54f6cb1a8c46f7b39cbb30f27cd3f492908da09b2d7/coverage-7.14.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:aa5e304a873fabddc11e484e9b6b738bd38bd7bed17b09aa84eecf5332e8b8bb", size = 262069, upload-time = "2026-05-26T20:40:11.999Z" }, + { url = "https://files.pythonhosted.org/packages/f0/59/2ae3cb79da554a06c8619d6c88ea19dd1e4aed4b834b6a83bb1fa243bdc5/coverage-7.14.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:5a1c5215be81035e629d5bc756650634d0bf31991038db7a0eccb90f025ce16d", size = 265780, upload-time = "2026-05-26T20:40:13.858Z" }, + { url = "https://files.pythonhosted.org/packages/af/5f/b130c1dc999031f2648bd25317fbce505ad8d5562079b4ed81e736a84967/coverage-7.14.1-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:79058c47dae6788504b5effb319961bcd72d7240551464b91d474bc0ed186d69", size = 260970, upload-time = "2026-05-26T20:40:16.142Z" }, + { url = "https://files.pythonhosted.org/packages/87/d1/ec13ccddeb48ec963bdfa72a11224bac2584bd045ba13beca82f8113e9c7/coverage-7.14.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:370c5afae3fa0658e11694a32b24c2778f6bc2d17718121f94ee185e69f26b54", size = 263157, upload-time = "2026-05-26T20:40:18.382Z" }, + { url = "https://files.pythonhosted.org/packages/cf/c2/cd91ead503045161092d3845f7bb95ea2f25131ce96d3e314dd835d91b9c/coverage-7.14.1-cp313-cp313t-win32.whl", hash = "sha256:3758dd0a7f1fa57365ef2e781df0f0731d38b6e3772259d13dae4bd8a958d4b1", size = 223259, upload-time = "2026-05-26T20:40:20.381Z" }, + { url = "https://files.pythonhosted.org/packages/71/9f/1e28d97e6bd2c76b07f38b7c02870f1371255ff6717f54eca578fcbbdd0e/coverage-7.14.1-cp313-cp313t-win_amd64.whl", hash = "sha256:6ff665fb023a77386fe11685190cee1f60a7d635994a30d9b0a061533d470fce", size = 224320, upload-time = "2026-05-26T20:40:22.316Z" }, + { url = "https://files.pythonhosted.org/packages/a9/e0/d936e908f0e1efa55e52b91e01b52f1055cef5e1ab2718493390ed8e2fb8/coverage-7.14.1-cp313-cp313t-win_arm64.whl", hash = "sha256:17a5a241e5997621a956a7f402a7433ef4221e5152809b785bec79e2323799f1", size = 222577, upload-time = "2026-05-26T20:40:24.894Z" }, + { url = "https://files.pythonhosted.org/packages/d6/34/fc2f101b151af3799a101f0550b0454aa008afdc0add677394ec4aa8ea10/coverage-7.14.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d5ed429d0b8edaac649e889b4ffcedb6c80b06629a3f93050e3dddfb99235bee", size = 220091, upload-time = "2026-05-26T20:40:27.249Z" }, + { url = "https://files.pythonhosted.org/packages/3d/a7/1ebae2ab5b961b5c79bb09fe7b3ac99edb190d8be4a8c510b2cf66f46468/coverage-7.14.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8011224a62280e50dab346960c03cf47aca1a1e09e608c0fb33fd6e0cc8e9500", size = 220421, upload-time = "2026-05-26T20:40:30.084Z" }, + { url = "https://files.pythonhosted.org/packages/5e/90/92aca9cf0acc95123c96cd1eb1f08917897a7f5dee01e15738922971ec31/coverage-7.14.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:12c42ec1e14f553c4f817e989365982e646e27211f10a0f717855b94a79c8906", size = 251466, upload-time = "2026-05-26T20:40:32.542Z" }, + { url = "https://files.pythonhosted.org/packages/26/2b/78048cbe3b999f6cbf9cc0d90abba6a88a3e0863a8c1c6cbc762f3f8802f/coverage-7.14.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:06144cd511cf2624873a035c5069cf297144f6e77a73ee3d7a55b605ec5efb42", size = 253973, upload-time = "2026-05-26T20:40:34.473Z" }, + { url = "https://files.pythonhosted.org/packages/8e/21/c2e33b29d1cfde484a19d437afc343c6cd30b08d78cbbf9f5aff14e57b2b/coverage-7.14.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a311d8e1da24be5c1ccf85cbfb06315dbaa1703d5a1eab3f6432c72b837917c8", size = 255318, upload-time = "2026-05-26T20:40:38.154Z" }, + { url = "https://files.pythonhosted.org/packages/8e/ee/aad2f108d63b769121005302f16bf66db8625c88ceaba466942e09a2607e/coverage-7.14.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c79cead5b5bc584d9c71451cb984d0e3a84e0c0937379c8efcbf27c8d661b851", size = 257633, upload-time = "2026-05-26T20:40:40.164Z" }, + { url = "https://files.pythonhosted.org/packages/c2/f8/11a2c29b4fd76d9849f81d0bb812ec0017a9396df3217214e38934a8c837/coverage-7.14.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:dcbf65f1f66a26cdd88c35cf68fb4729c5d1cd2e88added72420541dfb212034", size = 251488, upload-time = "2026-05-26T20:40:42.631Z" }, + { url = "https://files.pythonhosted.org/packages/c9/b8/9a5820de4b8ac2b71d85e3b5fb49108d7469c665f0e2ad0dd7569023e305/coverage-7.14.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fd86572566fb40189a8260446158235159bc7a82dfbc87a3b39cf4fb57fcec1c", size = 253329, upload-time = "2026-05-26T20:40:45.208Z" }, + { url = "https://files.pythonhosted.org/packages/6b/ff/f33e4823667e27548e8fd8df44217515303f9808d0ff29817db56f87d990/coverage-7.14.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:7771b601718fdde84832c3a434ca9bbf4ae9adbc49d84198b4110700c3c77c36", size = 251291, upload-time = "2026-05-26T20:40:47.502Z" }, + { url = "https://files.pythonhosted.org/packages/68/9b/489db0ebb209054766b90a9014a45f6d26eb724c02ec21311c3733b5a644/coverage-7.14.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:39b21e212c55af06fa375e3dbf90a8a8e38792f3a910c580066d23563830ddd5", size = 255564, upload-time = "2026-05-26T20:40:49.372Z" }, + { url = "https://files.pythonhosted.org/packages/27/b5/16bc2d4c2409b23c7737edb68c83bc89e345f378050549fe1d75ac7d34d5/coverage-7.14.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:f2302660e32562a532b442480121aef8aa61a5bdb20b30bf0adab29f10a5a4b4", size = 251107, upload-time = "2026-05-26T20:40:51.677Z" }, + { url = "https://files.pythonhosted.org/packages/7d/0c/2629997469a00cd069d588a41c9dc887610f2775ae89d250c4791e65272a/coverage-7.14.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:03a6f93c1ec3b7f2e77b5dbcc5573a2c21f12529a5c6bbe0f16f72303cc2fa4d", size = 252764, upload-time = "2026-05-26T20:40:54.267Z" }, + { url = "https://files.pythonhosted.org/packages/d2/ee/f78d63c8f079e0d7211c7e2401fa17e311514534ba61bae03e4b287ce4ab/coverage-7.14.1-cp314-cp314-win32.whl", hash = "sha256:8a3ce026d73290f42f08dafecbd82c193a74df280461fbf97300fec51fd133ee", size = 222837, upload-time = "2026-05-26T20:40:56.496Z" }, + { url = "https://files.pythonhosted.org/packages/dc/b9/be539854f93a70dfbeec69117f33ec70dc42ff0b65b5b07ab8d40d04228e/coverage-7.14.1-cp314-cp314-win_amd64.whl", hash = "sha256:114c95ef29302423b87d159075805f4ab973254a2638a5d7d046c94887cc87d7", size = 223650, upload-time = "2026-05-26T20:40:58.351Z" }, + { url = "https://files.pythonhosted.org/packages/fe/9e/24e2842fef40f35ac82ba3a7719c8023d011bf3bf652d0675316a9d088a1/coverage-7.14.1-cp314-cp314-win_arm64.whl", hash = "sha256:a07891c3f4805442b31b71e84ba3cf29ed1aa9a428284e06deeb4b23e5b46343", size = 222218, upload-time = "2026-05-26T20:41:00.321Z" }, + { url = "https://files.pythonhosted.org/packages/0a/1d/ac0a9df5fe31c1e8bdd658074905fc12844a05c1a7e3fdb8417e97c31e23/coverage-7.14.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:1101a5ebb083aecb625ebb6209d4105b58f647b093cb2dc8122d7b33f743cfe1", size = 220822, upload-time = "2026-05-26T20:41:02.281Z" }, + { url = "https://files.pythonhosted.org/packages/32/cf/f964fd9aff20323f9f1a726c97135f8a76bcd87b92dad141a456a43f3c64/coverage-7.14.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:851b9e1e4e8a4608e77c79714b2e77c0970d2ed7202a05e92ae407817481887b", size = 221084, upload-time = "2026-05-26T20:41:04.593Z" }, + { url = "https://files.pythonhosted.org/packages/d8/5e/7e5ef2aba844de2b80d678619fcf0841b42e3f37f16411226f3fe4c1016f/coverage-7.14.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d5b89cdfb2ee051b71e8c3c70bd81a9eff81100f736a269136fe1a68efe00474", size = 262454, upload-time = "2026-05-26T20:41:06.641Z" }, + { url = "https://files.pythonhosted.org/packages/64/62/75809bded87015cc4935524218a2a8ed8dd1a8498bfed30a2f4f7a4b4d34/coverage-7.14.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0177614a0370f227888b4e436a7c55686d6a9f90eb1ade2b624ba685a1686e86", size = 264578, upload-time = "2026-05-26T20:41:08.556Z" }, + { url = "https://files.pythonhosted.org/packages/f3/42/d33392dc14633525012d2d504fa1a33b05538bf535f5c1d64675e5754b78/coverage-7.14.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2d69af5dea2de76fc485a83032a630523f985198b7e25be901ec60181587b01e", size = 266981, upload-time = "2026-05-26T20:41:10.824Z" }, + { url = "https://files.pythonhosted.org/packages/2a/49/0157c4428c2aca7f1e09d5565930586fd5ae36f1655f08b0daa7cf1fcae1/coverage-7.14.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:35ab22d91de736e8966b980dc355cbcdd2c6dbbcfe275f9a2991bc8a91b3df65", size = 268112, upload-time = "2026-05-26T20:41:12.966Z" }, + { url = "https://files.pythonhosted.org/packages/96/26/86b9ce71f4092b1ed325ce1421698081df1286b833400b6836912834d6e0/coverage-7.14.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:357d4e32935c36588aaba057d734fa32428c360c9fc2e4442afbf1b646beee6e", size = 261558, upload-time = "2026-05-26T20:41:15Z" }, + { url = "https://files.pythonhosted.org/packages/20/4c/c311210c5472cf5401d8422b0d7812cdd520f24417673afabda6c323faca/coverage-7.14.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:51bd64741cc6fa065abd300ede1afe5a5291ece9c31da8b24884deda48bcc3f8", size = 264447, upload-time = "2026-05-26T20:41:17.369Z" }, + { url = "https://files.pythonhosted.org/packages/fb/71/59513f8710ed3e6b0ac0a050a5b7e977bb9c9e880354863b5d00d8809256/coverage-7.14.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:9132cd363a68a4c3daa7c8704a654b1e39d3360f6f5b8ddd470608a945236c07", size = 262048, upload-time = "2026-05-26T20:41:19.309Z" }, + { url = "https://files.pythonhosted.org/packages/84/8d/bceed32dc494f5bbf50f775cd2e78ca814953942b5ea28d3c1c3ac316f14/coverage-7.14.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:07c6290b1697b862c0478eab545eec949a0d0e4d6d03497f446d706da3b4f2de", size = 265781, upload-time = "2026-05-26T20:41:21.559Z" }, + { url = "https://files.pythonhosted.org/packages/e7/c5/9348fe40dbfd4991aaf78df2c6c3098bfb2cc834d1fd362a64b4efef855a/coverage-7.14.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:5ea0c297e27133853b4d8a3eb799bff5a2dbd9f2f41537a240d337ac9b4df890", size = 260896, upload-time = "2026-05-26T20:41:23.428Z" }, + { url = "https://files.pythonhosted.org/packages/ca/92/1ea0f03929da7cf87206b1fa24f4c8e9c158be0455481af29ec0a1f3503f/coverage-7.14.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:01b7733daad0237daa01ef80fe2dfceffc911e6a17fa7b55d14aa8214eaaaecd", size = 263214, upload-time = "2026-05-26T20:41:25.419Z" }, + { url = "https://files.pythonhosted.org/packages/f6/a9/b2493c054c0e01a643266742ab45e15744e60743f9260cd930c7142b1124/coverage-7.14.1-cp314-cp314t-win32.whl", hash = "sha256:6adc5a36984624a70bf11d7184e20fa0a49aa7c47ffab43804106a1a695ea22e", size = 223624, upload-time = "2026-05-26T20:41:27.795Z" }, + { url = "https://files.pythonhosted.org/packages/fc/bd/3e1e6a57fccd2d7c83fcdf338e93ba98eb85c6e877dd34731ac585375490/coverage-7.14.1-cp314-cp314t-win_amd64.whl", hash = "sha256:ddf799247318f34dbcd2efa8c95a8d0642674e926bb1774cf9b63dfd2a389d1c", size = 224728, upload-time = "2026-05-26T20:41:30.098Z" }, + { url = "https://files.pythonhosted.org/packages/bb/d7/31066cf1d2f0c6c797fce911bcfa01dd35642dc6da992a950256097c5860/coverage-7.14.1-cp314-cp314t-win_arm64.whl", hash = "sha256:145986fe66647eb489f18d9a997567a3fd358584c4b5a808769113abc07466af", size = 222752, upload-time = "2026-05-26T20:41:32.123Z" }, + { url = "https://files.pythonhosted.org/packages/8a/3c/1a983b9a745d7f83d53f057bcc5bf79ba6a2bbc08266b3f0c7d6fe630c9b/coverage-7.14.1-py3-none-any.whl", hash = "sha256:a252f21c27e38347e60111a3266b03827422a7d5525951aceee313aa68bab1d2", size = 211815, upload-time = "2026-05-26T20:41:34.078Z" }, +] + [[package]] name = "iniconfig" version = "2.3.0" @@ -131,11 +131,11 @@ wheels = [ [[package]] name = "packaging" -version = "26.0" +version = "26.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134, upload-time = "2026-04-24T20:15:23.917Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" }, + { url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" }, ] [[package]] @@ -149,16 +149,16 @@ wheels = [ [[package]] name = "pygments" -version = "2.19.2" +version = "2.20.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, + { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, ] [[package]] name = "pytest" -version = "9.0.2" +version = "9.0.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, @@ -167,9 +167,9 @@ dependencies = [ { name = "pluggy" }, { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/0d/549bd94f1a0a402dc8cf64563a117c0f3765662e2e668477624baeec44d5/pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c", size = 1572165, upload-time = "2026-04-07T17:16:18.027Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, + { url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249, upload-time = "2026-04-07T17:16:16.13Z" }, ] [[package]] @@ -234,25 +234,25 @@ wheels = [ [[package]] name = "ruff" -version = "0.15.7" +version = "0.15.15" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a1/22/9e4f66ee588588dc6c9af6a994e12d26e19efbe874d1a909d09a6dac7a59/ruff-0.15.7.tar.gz", hash = "sha256:04f1ae61fc20fe0b148617c324d9d009b5f63412c0b16474f3d5f1a1a665f7ac", size = 4601277, upload-time = "2026-03-19T16:26:22.605Z" } +sdist = { url = "https://files.pythonhosted.org/packages/84/6f/a76f7d96e5c962f5b69cee865e49c15c1116897c01990faa8a57edb62e7f/ruff-0.15.15.tar.gz", hash = "sha256:b8dff018130b46d8e5bf0f926ef6b60cf871d6d5ae45fc9334e09632daa741d6", size = 4706985, upload-time = "2026-05-28T14:16:57.784Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/41/2f/0b08ced94412af091807b6119ca03755d651d3d93a242682bf020189db94/ruff-0.15.7-py3-none-linux_armv6l.whl", hash = "sha256:a81cc5b6910fb7dfc7c32d20652e50fa05963f6e13ead3c5915c41ac5d16668e", size = 10489037, upload-time = "2026-03-19T16:26:32.47Z" }, - { url = "https://files.pythonhosted.org/packages/91/4a/82e0fa632e5c8b1eba5ee86ecd929e8ff327bbdbfb3c6ac5d81631bef605/ruff-0.15.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:722d165bd52403f3bdabc0ce9e41fc47070ac56d7a91b4e0d097b516a53a3477", size = 10955433, upload-time = "2026-03-19T16:27:00.205Z" }, - { url = "https://files.pythonhosted.org/packages/ab/10/12586735d0ff42526ad78c049bf51d7428618c8b5c467e72508c694119df/ruff-0.15.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7fbc2448094262552146cbe1b9643a92f66559d3761f1ad0656d4991491af49e", size = 10269302, upload-time = "2026-03-19T16:26:26.183Z" }, - { url = "https://files.pythonhosted.org/packages/eb/5d/32b5c44ccf149a26623671df49cbfbd0a0ae511ff3df9d9d2426966a8d57/ruff-0.15.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b39329b60eba44156d138275323cc726bbfbddcec3063da57caa8a8b1d50adf", size = 10607625, upload-time = "2026-03-19T16:27:03.263Z" }, - { url = "https://files.pythonhosted.org/packages/5d/f1/f0001cabe86173aaacb6eb9bb734aa0605f9a6aa6fa7d43cb49cbc4af9c9/ruff-0.15.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:87768c151808505f2bfc93ae44e5f9e7c8518943e5074f76ac21558ef5627c85", size = 10324743, upload-time = "2026-03-19T16:27:09.791Z" }, - { url = "https://files.pythonhosted.org/packages/7a/87/b8a8f3d56b8d848008559e7c9d8bf367934d5367f6d932ba779456e2f73b/ruff-0.15.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fb0511670002c6c529ec66c0e30641c976c8963de26a113f3a30456b702468b0", size = 11138536, upload-time = "2026-03-19T16:27:06.101Z" }, - { url = "https://files.pythonhosted.org/packages/e4/f2/4fd0d05aab0c5934b2e1464784f85ba2eab9d54bffc53fb5430d1ed8b829/ruff-0.15.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e0d19644f801849229db8345180a71bee5407b429dd217f853ec515e968a6912", size = 11994292, upload-time = "2026-03-19T16:26:48.718Z" }, - { url = "https://files.pythonhosted.org/packages/64/22/fc4483871e767e5e95d1622ad83dad5ebb830f762ed0420fde7dfa9d9b08/ruff-0.15.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4806d8e09ef5e84eb19ba833d0442f7e300b23fe3f0981cae159a248a10f0036", size = 11398981, upload-time = "2026-03-19T16:26:54.513Z" }, - { url = "https://files.pythonhosted.org/packages/b0/99/66f0343176d5eab02c3f7fcd2de7a8e0dd7a41f0d982bee56cd1c24db62b/ruff-0.15.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dce0896488562f09a27b9c91b1f58a097457143931f3c4d519690dea54e624c5", size = 11242422, upload-time = "2026-03-19T16:26:29.277Z" }, - { url = "https://files.pythonhosted.org/packages/5d/3a/a7060f145bfdcce4c987ea27788b30c60e2c81d6e9a65157ca8afe646328/ruff-0.15.7-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:1852ce241d2bc89e5dc823e03cff4ce73d816b5c6cdadd27dbfe7b03217d2a12", size = 11232158, upload-time = "2026-03-19T16:26:42.321Z" }, - { url = "https://files.pythonhosted.org/packages/a7/53/90fbb9e08b29c048c403558d3cdd0adf2668b02ce9d50602452e187cd4af/ruff-0.15.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:5f3e4b221fb4bd293f79912fc5e93a9063ebd6d0dcbd528f91b89172a9b8436c", size = 10577861, upload-time = "2026-03-19T16:26:57.459Z" }, - { url = "https://files.pythonhosted.org/packages/2f/aa/5f486226538fe4d0f0439e2da1716e1acf895e2a232b26f2459c55f8ddad/ruff-0.15.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:b15e48602c9c1d9bdc504b472e90b90c97dc7d46c7028011ae67f3861ceba7b4", size = 10327310, upload-time = "2026-03-19T16:26:35.909Z" }, - { url = "https://files.pythonhosted.org/packages/99/9e/271afdffb81fe7bfc8c43ba079e9d96238f674380099457a74ccb3863857/ruff-0.15.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:1b4705e0e85cedc74b0a23cf6a179dbb3df184cb227761979cc76c0440b5ab0d", size = 10840752, upload-time = "2026-03-19T16:26:45.723Z" }, - { url = "https://files.pythonhosted.org/packages/bf/29/a4ae78394f76c7759953c47884eb44de271b03a66634148d9f7d11e721bd/ruff-0.15.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:112c1fa316a558bb34319282c1200a8bf0495f1b735aeb78bfcb2991e6087580", size = 11336961, upload-time = "2026-03-19T16:26:39.076Z" }, - { url = "https://files.pythonhosted.org/packages/26/6b/8786ba5736562220d588a2f6653e6c17e90c59ced34a2d7b512ef8956103/ruff-0.15.7-py3-none-win32.whl", hash = "sha256:6d39e2d3505b082323352f733599f28169d12e891f7dd407f2d4f54b4c2886de", size = 10582538, upload-time = "2026-03-19T16:26:15.992Z" }, - { url = "https://files.pythonhosted.org/packages/2b/e9/346d4d3fffc6871125e877dae8d9a1966b254fbd92a50f8561078b88b099/ruff-0.15.7-py3-none-win_amd64.whl", hash = "sha256:4d53d712ddebcd7dace1bc395367aec12c057aacfe9adbb6d832302575f4d3a1", size = 11755839, upload-time = "2026-03-19T16:26:19.897Z" }, - { url = "https://files.pythonhosted.org/packages/8f/e8/726643a3ea68c727da31570bde48c7a10f1aa60eddd628d94078fec586ff/ruff-0.15.7-py3-none-win_arm64.whl", hash = "sha256:18e8d73f1c3fdf27931497972250340f92e8c861722161a9caeb89a58ead6ed2", size = 11023304, upload-time = "2026-03-19T16:26:51.669Z" }, + { url = "https://files.pythonhosted.org/packages/fa/9d/3a45c05b8ab04b4705989de70a79008e27c8003296a0feaee9edc18dd7e9/ruff-0.15.15-py3-none-linux_armv6l.whl", hash = "sha256:cf93e5388f412e1b108b1f8b34a6e036b70fe8aff89393befad96fe48670311b", size = 10710652, upload-time = "2026-05-28T14:16:06.701Z" }, + { url = "https://files.pythonhosted.org/packages/05/66/da974431624bf3b49f6ee1f9543c02d929ff1cba78b0d5a79c38cf21f744/ruff-0.15.15-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ac5a646d1f6a7dadd5d50842dae2c1f9862ac887ef5d1b1375e02def791fde6e", size = 11096615, upload-time = "2026-05-28T14:16:23.313Z" }, + { url = "https://files.pythonhosted.org/packages/8c/09/7443452e5d290230a712103f2fdceeef7184f3ec99a2bd01c8be78aaceb5/ruff-0.15.15-py3-none-macosx_11_0_arm64.whl", hash = "sha256:77d955a431430c66f72dd94e379ad38a16daea3d25094872ac4edf9e797be530", size = 10436683, upload-time = "2026-05-28T14:16:40.974Z" }, + { url = "https://files.pythonhosted.org/packages/53/01/d330c26a57fa4f3943a14424904027428315b700fe4d14a84bb123a649e5/ruff-0.15.15-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7614ee79c69788cf6cedd568069ade9cecc22a1ad20494efe8d0c9ebb4b622d4", size = 10769064, upload-time = "2026-05-28T14:16:28.905Z" }, + { url = "https://files.pythonhosted.org/packages/1d/85/cc8770f8bdff541b1da8392d1634141fe4a0e3f4ee596605959b7906c27f/ruff-0.15.15-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3cdb1679e06a1f6b47bc384714ae96f6e2fb65ca441eb78c43d2ca554176ce1f", size = 10511987, upload-time = "2026-05-28T14:16:43.732Z" }, + { url = "https://files.pythonhosted.org/packages/7c/29/8c190c1472b63013583ba391f3342036e02010544c1270455ed8e519bdf3/ruff-0.15.15-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2728b93d7b23a603ea2c0ac6eb73d760bd38ec9de35f35fb41e18f7a3fee7622", size = 11275100, upload-time = "2026-05-28T14:16:55.244Z" }, + { url = "https://files.pythonhosted.org/packages/9f/6b/7e145ce2cc8e63d6834eca03d83a0e18d121def5c69f91b4cf4011ed4879/ruff-0.15.15-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be582fcc0db438902c7792b08d6ddf6c9b9e21addaa10092c2c741cfb09e5a45", size = 12176903, upload-time = "2026-05-28T14:16:14.368Z" }, + { url = "https://files.pythonhosted.org/packages/80/a3/d5974637f68e451f7fadf015cf3101d1cd7d8ba5027cffe0b9e3826ebe6b/ruff-0.15.15-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7aa77465b8ecaf1a27bea098d696f7fed5e1eccbd10b321b682d6de586ae5627", size = 11404550, upload-time = "2026-05-28T14:16:20.138Z" }, + { url = "https://files.pythonhosted.org/packages/fe/1c/e6e5e568f22be4fb05d6244234aba384c06b451252453b821e1a529263cf/ruff-0.15.15-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48decfa11d740de4889de623be1463308346312f2409a56e24aa280c86162dc4", size = 11382027, upload-time = "2026-05-28T14:16:46.615Z" }, + { url = "https://files.pythonhosted.org/packages/1d/01/170921b49fcd2e8858825593f91cf7146c3e40a5c3e6df763e4bb0484dde/ruff-0.15.15-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:a5015088452ca0081387063649ec67f06d3d1d6b8b936a1f836b5e9657ecd48c", size = 11366041, upload-time = "2026-05-28T14:16:26.247Z" }, + { url = "https://files.pythonhosted.org/packages/87/54/a7bad711d7de93254e15e06a4c375b89a03d18de45d3e5dcc86a4472fb1a/ruff-0.15.15-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:f5294aab6356c81600fcdea3a62bb1b924dfd5e91767c12318d3f68f86af57cd", size = 10741795, upload-time = "2026-05-28T14:16:17.11Z" }, + { url = "https://files.pythonhosted.org/packages/c9/31/38c075963668f8b41c6914ee0f6f318727fbe30ab9145cb29e6df464c5fa/ruff-0.15.15-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:db5bd4d802415cca656dc1616070b725952d6ae95eb5d4831e49fbd94a38f75f", size = 10511117, upload-time = "2026-05-28T14:16:31.767Z" }, + { url = "https://files.pythonhosted.org/packages/9d/96/6ff689e1f7e375d1d97075eca022f74c2bab59554a432fe4d2e6f091986a/ruff-0.15.15-py3-none-musllinux_1_2_i686.whl", hash = "sha256:587a6278ed42059191c1a466e490bd7930fb50bd2e255398bc29616c895a61cb", size = 10994867, upload-time = "2026-05-28T14:16:35.149Z" }, + { url = "https://files.pythonhosted.org/packages/c3/c2/5dce0ab9f92a8d534fa62b9bf9caca3eddb8c1a81b616f5e195ada4f0d6e/ruff-0.15.15-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:df0c1c084f5f4be9812f61518a45c440d3c30d69ce4bf6c5270e66d38338f02a", size = 11482101, upload-time = "2026-05-28T14:16:49.598Z" }, + { url = "https://files.pythonhosted.org/packages/b1/c0/1003b60edd697c649faf61f1a34094b1abb38fb3d1181e3f895781250a08/ruff-0.15.15-py3-none-win32.whl", hash = "sha256:29428ea79694afbe756d45fd59b36f22b6b020dc0443cf7de0173046236964b9", size = 10716774, upload-time = "2026-05-28T14:16:52.337Z" }, + { url = "https://files.pythonhosted.org/packages/02/a8/1269eddd6945a06c23f055ef7848886e37cf9d6a8bebb386a3115f01470c/ruff-0.15.15-py3-none-win_amd64.whl", hash = "sha256:8df0323902e15e24bc4bf246da830573d3cf3352bd0b9a164eab335d111ff4a4", size = 11868463, upload-time = "2026-05-28T14:16:11.333Z" }, + { url = "https://files.pythonhosted.org/packages/4e/b2/920464c907b191e37469d477a1aa8bc048b8f36c4c1610dfa4ab87b39e18/ruff-0.15.15-py3-none-win_arm64.whl", hash = "sha256:3c8ceca6792f38196b8f589bc92eccd03eef286602da92e5dc05cc42ef6441b7", size = 11138498, upload-time = "2026-05-28T14:16:38.425Z" }, ] diff --git a/converters/index.md b/converters/index.md index 6477291b..1ceb3b83 100644 --- a/converters/index.md +++ b/converters/index.md @@ -1,14 +1,33 @@ -# OSI Converters + + +# Apache Ossie Converters ## Overview -An OSI Converter translates between the OSI semantic model format and a specific vendor's semantic implementation. This enables teams to author a semantic model once in the OSI standard and then generate the corresponding vendor-specific representation automatically. +An Ossie Converter translates between the Ossie semantic model format and a specific vendor's semantic implementation. This enables teams to author a semantic model once in the Ossie standard and then generate the corresponding vendor-specific representation automatically. ## Hub-and-Spoke Model -OSI converters follow a **hub-and-spoke** architecture: +Ossie converters follow a **hub-and-spoke** architecture: -- **Hub**: The OSI core specification acts as the central, vendor-neutral format. +- **Hub**: The Ossie core specification acts as the central, vendor-neutral format. - **Spokes**: Each converter handles translation to or from a specific vendor format. ``` @@ -17,7 +36,7 @@ OSI converters follow a **hub-and-spoke** architecture: └──────┬──────┘ │ ┌─────────────┐ ┌─────┴─────┐ ┌─────────────┐ -│ dbt ├────┤ OSI ├────┤ Salesforce │ +│ dbt ├────┤ Ossie ├────┤ Salesforce │ └─────────────┘ └─────┬─────┘ └─────────────┘ │ ┌──────┴──────┐ @@ -25,28 +44,28 @@ OSI converters follow a **hub-and-spoke** architecture: └─────────────┘ ``` -This approach avoids the need for point-to-point converters between every pair of vendors. With N vendors, a point-to-point strategy would require N*(N-1) converters. With OSI as the hub, only 2*N converters are needed (one import and one export per vendor), and interoperability with all other vendors comes for free. +This approach avoids the need for point-to-point converters between every pair of vendors. With N vendors, a point-to-point strategy would require N*(N-1) converters. With Ossie as the hub, only 2*N converters are needed (one import and one export per vendor), and interoperability with all other vendors comes for free. ## Converter Responsibilities A converter handles two directions of translation: -### Export (OSI → Vendor) +### Export (Apache Ossie → Vendor) -Read an OSI semantic model and produce the equivalent vendor-specific representation. For example: +Read an Ossie semantic model and produce the equivalent vendor-specific representation. For example: -- OSI → Snowflake semantic model definition -- OSI → dbt `semantic_models` YAML -- OSI → Tableau data source / Salesforce semantic layer -- OSI → Databricks semantic layer definition +- Ossie → Snowflake semantic model definition +- Ossie → dbt `semantic_models` YAML +- Ossie → Tableau data source / Salesforce semantic layer +- Ossie → Databricks semantic layer definition -### Import (Vendor → OSI) +### Import (Vendor → Apache Ossie) -Read a vendor-specific semantic model and produce a valid OSI model, including mapping vendor-specific metadata into `custom_extensions`. +Read a vendor-specific semantic model and produce a valid Ossie model, including mapping vendor-specific metadata into `custom_extensions`. ## Supported Vendors -The OSI specification currently defines extensions for the following vendors: +The Ossie specification currently defines extensions for the following vendors: | Vendor | Description | |--------|-------------| @@ -55,17 +74,17 @@ The OSI specification currently defines extensions for the following vendors: | `DBT` | dbt semantic models | | `DATABRICKS` | Databricks semantic layer | -Each vendor may define custom extensions (via the `custom_extensions` field in the OSI spec) to carry vendor-specific metadata that does not have an equivalent in the core specification. +Each vendor may define custom extensions (via the `custom_extensions` field in the Ossie spec) to carry vendor-specific metadata that does not have an equivalent in the core specification. ## Mapping Core Constructs -The following table shows how each OSI construct maps conceptually to vendor equivalents. A converter must handle each of these: +The following table shows how each Ossie construct maps conceptually to vendor equivalents. A converter must handle each of these: ### Semantic Model The top-level container. Maps to the root object in each vendor's format. -| OSI Field | Description | Converter Consideration | +| Ossie Field | Description | Converter Consideration | |-----------|-------------|------------------------| | `name` | Model identifier | Map to vendor's model/project name | | `description` | Human-readable description | Most vendors support a description field | @@ -79,7 +98,7 @@ The top-level container. Maps to the root object in each vendor's format. Datasets represent logical tables (fact or dimension tables). They contain fields and define the structure of the data. -| OSI Field | Description | Converter Consideration | +| Ossie Field | Description | Converter Consideration | |-----------|-------------|------------------------| | `name` | Dataset identifier | Map to table/entity/model name | | `source` | Physical table reference (e.g., `database.schema.table`) | Parse into vendor-specific catalog/schema/table components | @@ -93,7 +112,7 @@ Datasets represent logical tables (fact or dimension tables). They contain field Fields represent row-level attributes. They can be simple column references or computed expressions. -| OSI Field | Description | Converter Consideration | +| Ossie Field | Description | Converter Consideration | |-----------|-------------|------------------------| | `name` | Field identifier | Map to column/attribute name | | `expression.dialects` | Multi-dialect SQL expressions | Select the dialect matching the target vendor; fall back to `ANSI_SQL` | @@ -119,7 +138,7 @@ A Snowflake converter should pick the `SNOWFLAKE` dialect when available, and fa Relationships define foreign key connections between datasets. They support both simple and composite keys. -| OSI Field | Description | Converter Consideration | +| Ossie Field | Description | Converter Consideration | |-----------|-------------|------------------------| | `name` | Relationship identifier | Map to vendor's join/relationship name | | `from` | Many-side dataset | Map to the referencing table | @@ -140,7 +159,7 @@ means `from.product_id = to.id AND from.variant_id = to.variant_id`. The convert Metrics are aggregate measures defined at the semantic model level. They can span multiple datasets via relationships. -| OSI Field | Description | Converter Consideration | +| Ossie Field | Description | Converter Consideration | |-----------|-------------|------------------------| | `name` | Metric identifier | Map to vendor's measure/KPI name | | `expression.dialects` | Multi-dialect aggregate expressions | Select the appropriate dialect; fall back to `ANSI_SQL` | @@ -163,8 +182,8 @@ The converter must ensure that the dataset references (`store_sales`, `customer` Custom extensions carry vendor-specific metadata as a JSON string. A converter should: -1. **On export (OSI → Vendor)**: Extract extensions where `vendor_name` matches the target vendor, parse the `data` JSON, and apply the vendor-specific settings. -2. **On import (Vendor → OSI)**: Capture vendor-specific settings that have no OSI core equivalent and store them as a `custom_extension` entry. +1. **On export (Ossie → Vendor)**: Extract extensions where `vendor_name` matches the target vendor, parse the `data` JSON, and apply the vendor-specific settings. +2. **On import (Vendor → Ossie)**: Capture vendor-specific settings that have no Ossie core equivalent and store them as a `custom_extension` entry. **Example**: A Snowflake export converter would extract: @@ -200,9 +219,9 @@ A converter should map `ai_context` when the target vendor supports equivalent c ### Step-by-Step Guide -1. **Validate input**: Use the [OSI JSON Schema](../core-spec/osi-schema.json) and the [validation script](../validation/validate.py) to ensure the source OSI model is valid before conversion. +1. **Validate input**: Use the [Ossie JSON Schema](../core-spec/osi-schema.json) and the [validation script](../validation/validate.py) to ensure the source Ossie model is valid before conversion. -2. **Parse the OSI model**: Load the YAML file and iterate over the top-level `semantic_model` entries. +2. **Parse the Ossie model**: Load the YAML file and iterate over the top-level `semantic_model` entries. 3. **Map datasets**: For each dataset, translate the `name`, `source`, `primary_key`, `unique_keys`, and `fields` to the vendor's format. Parse the `source` string (typically `database.schema.table`) into the vendor's catalog structure. @@ -226,7 +245,7 @@ A converter should map `ai_context` when the target vendor supports equivalent c | Scenario | Recommended Approach | |----------|---------------------| | Missing vendor-specific dialect for a field/metric | Fall back to `ANSI_SQL`; log a warning | -| Computed fields with vendor-specific SQL syntax | Require the vendor dialect in the source OSI model; error if neither vendor nor ANSI dialect is available | +| Computed fields with vendor-specific SQL syntax | Require the vendor dialect in the source Ossie model; error if neither vendor nor ANSI dialect is available | | Composite primary keys | Ensure the vendor format supports composite keys; if not, flatten or document the limitation | | Cross-dataset metrics referencing multiple tables | Ensure all referenced datasets exist and relationships are defined; resolve qualified column names | | Custom extensions with unknown vendor | Ignore (do not discard) — preserve for round-tripping | @@ -237,14 +256,14 @@ A converter should map `ai_context` when the target vendor supports equivalent c A well-implemented converter pair (import + export) should preserve as much information as possible during round-tripping: ``` -Vendor A model → [Import] → OSI model → [Export] → Vendor A model +Vendor A model → [Import] → Ossie model → [Export] → Vendor A model ``` To achieve this: -- **Never discard information silently**. If a vendor-specific attribute has no OSI core equivalent, store it in `custom_extensions`. +- **Never discard information silently**. If a vendor-specific attribute has no Ossie core equivalent, store it in `custom_extensions`. - **Preserve field ordering** where possible, as some vendors are sensitive to declaration order. -- **Preserve `custom_extensions`** for all vendors, not just the target vendor. This allows a model to carry metadata for multiple vendors simultaneously (e.g., a single OSI model with both Snowflake and dbt extensions). +- **Preserve `custom_extensions`** for all vendors, not just the target vendor. This allows a model to carry metadata for multiple vendors simultaneously (e.g., a single Ossie model with both Snowflake and dbt extensions). ## Example: Conceptual Conversion Flow @@ -256,7 +275,7 @@ Given the [TPC-DS example](../examples/tpcds_semantic_model.yaml) included in th 4. For each field, select the `ANSI_SQL` dialect expression (since no `SNOWFLAKE` dialect is provided in this example). 5. Translate relationships into Snowflake join definitions, mapping `from_columns` and `to_columns` pairs. 6. Translate metrics like `total_sales` (`SUM(store_sales.ss_ext_sales_price)`) into Snowflake metric definitions. -7. Extract the `SALESFORCE` and `DBT` custom extensions — these would not be applied to the Snowflake output but should be preserved if the model is later imported back to OSI. +7. Extract the `SALESFORCE` and `DBT` custom extensions — these would not be applied to the Snowflake output but should be preserved if the model is later imported back to Ossie. ## Contributing a New Converter @@ -264,7 +283,7 @@ To add support for a new vendor: 1. Add the vendor to the `vendors` enum in the [core specification](../core-spec/spec.md) if not already present. 2. Define the custom extension schema for the vendor (what vendor-specific metadata fields are supported in the `data` JSON). -3. Implement the export converter (OSI → Vendor). -4. Implement the import converter (Vendor → OSI). +3. Implement the export converter (Ossie → Vendor). +4. Implement the import converter (Vendor → Ossie). 5. Add tests using the [TPC-DS example model](../examples/tpcds_semantic_model.yaml) as a baseline. 6. Document any limitations or unsupported constructs. diff --git a/converters/polaris/README.md b/converters/polaris/README.md index 801bbd57..d24aac7d 100644 --- a/converters/polaris/README.md +++ b/converters/polaris/README.md @@ -1,8 +1,27 @@ -# OSI Polaris Converter + + +# Apache Ossie Polaris Converter + +A two-way converter between [Ossie semantic models](../../core-spec/spec.md) and [Apache Polaris](https://polaris.apache.org/) catalogs. + +Apache Polaris is an open-source catalog for Apache Iceberg. This converter communicates with Polaris via the Iceberg REST Catalog API to import catalog metadata into Ossie format and export Ossie models back into Polaris. ## Building @@ -14,12 +33,12 @@ Requires Java 11+. ## Usage -### Import (Polaris → OSI) +### Import (Polaris → Apache Ossie) -Reads all namespaces and tables from a Polaris catalog and generates an OSI YAML file. +Reads all namespaces and tables from a Polaris catalog and generates an Ossie YAML file. ```bash -java -jar target/osi-polaris-converter-0.1.0-SNAPSHOT.jar import \ +java -jar target/ossie-polaris-converter-0.1.0-SNAPSHOT.jar import \ --url http://localhost:8181 \ --catalog my_catalog \ --client-id \ @@ -27,14 +46,14 @@ java -jar target/osi-polaris-converter-0.1.0-SNAPSHOT.jar import \ -o output.yaml ``` -Each Polaris namespace becomes a separate OSI semantic model containing datasets for every table in that namespace. +Each Polaris namespace becomes a separate Ossie semantic model containing datasets for every table in that namespace. -### Export (OSI → Polaris) +### Export (Apache Ossie → Polaris) -Reads an OSI YAML file and creates namespaces and Iceberg tables in a Polaris catalog. +Reads an Ossie YAML file and creates namespaces and Iceberg tables in a Polaris catalog. ```bash -java -jar target/osi-polaris-converter-0.1.0-SNAPSHOT.jar export \ +java -jar target/ossie-polaris-converter-0.1.0-SNAPSHOT.jar export \ --url http://localhost:8181 \ --catalog my_catalog \ --client-id \ @@ -42,7 +61,7 @@ java -jar target/osi-polaris-converter-0.1.0-SNAPSHOT.jar export \ model.yaml ``` -Each OSI semantic model becomes a Polaris namespace, and each dataset becomes an Iceberg table. +Each Ossie semantic model becomes a Polaris namespace, and each dataset becomes an Iceberg table. ### Options @@ -57,9 +76,9 @@ Each OSI semantic model becomes a Polaris namespace, and each dataset becomes an ## Mapping Reference -### Import (Polaris → OSI) +### Import (Polaris → Apache Ossie) -| Polaris / Iceberg | OSI | +| Polaris / Iceberg | Ossie | |-------------------|-----| | Namespace | `semantic_model` (name, description) | | Table | `dataset` (name) | @@ -69,9 +88,9 @@ Each OSI semantic model becomes a Polaris namespace, and each dataset becomes an | Temporal types (`timestamp`, `timestamptz`, `date`, `time`) | `field.dimension.is_time: true` | | Table properties | `dataset.custom_extensions` (vendor: `COMMON`) | -### Export (OSI → Polaris) +### Export (Apache Ossie → Polaris) -| OSI | Polaris / Iceberg | +| Ossie | Polaris / Iceberg | |-----|-------------------| | `semantic_model` | Namespace | | `dataset` | Table | @@ -83,7 +102,7 @@ Each OSI semantic model becomes a Polaris namespace, and each dataset becomes an ### Type Inference (Export) -Since OSI fields are expression-based and don't carry explicit types, the exporter infers Iceberg types using: +Since Ossie fields are expression-based and don't carry explicit types, the exporter infers Iceberg types using: 1. **Round-trip hints** — if a field description starts with `Iceberg type:` (produced by the importer), that type is used directly. 2. **Time dimensions** — fields with `dimension.is_time: true` map to `timestamptz`. @@ -106,7 +125,7 @@ Since OSI fields are expression-based and don't carry explicit types, the export │ │ ┌────────┴────────┐ ┌─────────┴─────────┐ │ PolarisImporter │ │ PolarisExporter │ - │ (Polaris → OSI) │ │ (OSI → Polaris) │ + │ (Polaris→Ossie) │ │ (Ossie→Polaris) │ └────────┬────────┘ └─────────┬─────────┘ │ │ ┌──────┴──────┐ ┌──────┴──────┐ @@ -116,7 +135,7 @@ Since OSI fields are expression-based and don't carry explicit types, the export ## Dependencies -- [SnakeYAML 2.2](https://bitbucket.org/snakeyaml/snakeyaml/) — OSI YAML parsing and generation +- [SnakeYAML 2.2](https://bitbucket.org/snakeyaml/snakeyaml/) — Ossie YAML parsing and generation - [Jackson Databind 2.17](https://github.com/FasterXML/jackson-databind) — JSON handling for Polaris REST API - [JUnit 5](https://junit.org/junit5/) — testing diff --git a/converters/polaris/pom.xml b/converters/polaris/pom.xml index cc613c40..6f945239 100644 --- a/converters/polaris/pom.xml +++ b/converters/polaris/pom.xml @@ -1,16 +1,42 @@ + + 4.0.0 - org.osi - osi-polaris-converter + + org.apache + apache + 39 + + + + org.apache.ossie + ossie-polaris-converter 0.1.0-SNAPSHOT jar - OSI Polaris Converter - Converts between OSI semantic models and Apache Polaris catalog metadata + Apache Ossie Polaris Converter + Converts between Apache Ossie semantic models and Apache Polaris catalog metadata 11 @@ -54,11 +80,33 @@ - org.osi.converter.polaris.OsiPolarisConverter + org.apache.ossie.converter.polaris.OsiPolarisConverter + + + + org.apache.rat + apache-rat-plugin + + + verify + + check + + + + + true + + **/target/** + + **/*.json + + + diff --git a/converters/polaris/src/main/java/org/osi/converter/polaris/OsiModelParser.java b/converters/polaris/src/main/java/org/apache/ossie/converter/polaris/OsiModelParser.java similarity index 85% rename from converters/polaris/src/main/java/org/osi/converter/polaris/OsiModelParser.java rename to converters/polaris/src/main/java/org/apache/ossie/converter/polaris/OsiModelParser.java index 8e0ea46b..8f9201ec 100644 --- a/converters/polaris/src/main/java/org/osi/converter/polaris/OsiModelParser.java +++ b/converters/polaris/src/main/java/org/apache/ossie/converter/polaris/OsiModelParser.java @@ -1,7 +1,26 @@ -package org.osi.converter.polaris; +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.ossie.converter.polaris; -import org.osi.converter.polaris.model.OsiModel; -import org.osi.converter.polaris.model.OsiModel.*; +import org.apache.ossie.converter.polaris.model.OsiModel; +import org.apache.ossie.converter.polaris.model.OsiModel.*; import org.yaml.snakeyaml.Yaml; import java.io.IOException; @@ -13,12 +32,12 @@ import java.util.Map; /** - * Parses an OSI YAML file into an {@link OsiModel}. + * Parses an Ossie YAML file into an {@link OsiModel}. */ public class OsiModelParser { /** - * Parse an OSI YAML file from the given path. + * Parse an Ossie YAML file from the given path. */ public OsiModel parse(Path yamlPath) throws IOException { try (InputStream is = Files.newInputStream(yamlPath)) { @@ -27,7 +46,7 @@ public OsiModel parse(Path yamlPath) throws IOException { } /** - * Parse an OSI YAML file from an input stream. + * Parse an Ossie YAML file from an input stream. */ @SuppressWarnings("unchecked") public OsiModel parse(InputStream is) { diff --git a/converters/polaris/src/main/java/org/osi/converter/polaris/OsiPolarisConverter.java b/converters/polaris/src/main/java/org/apache/ossie/converter/polaris/OsiPolarisConverter.java similarity index 73% rename from converters/polaris/src/main/java/org/osi/converter/polaris/OsiPolarisConverter.java rename to converters/polaris/src/main/java/org/apache/ossie/converter/polaris/OsiPolarisConverter.java index 46623b0e..c9eb50d7 100644 --- a/converters/polaris/src/main/java/org/osi/converter/polaris/OsiPolarisConverter.java +++ b/converters/polaris/src/main/java/org/apache/ossie/converter/polaris/OsiPolarisConverter.java @@ -1,6 +1,25 @@ -package org.osi.converter.polaris; +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.ossie.converter.polaris; -import org.osi.converter.polaris.model.OsiModel; +import org.apache.ossie.converter.polaris.model.OsiModel; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -8,18 +27,18 @@ import java.nio.file.Paths; /** - * CLI entry point for the OSI Polaris converter. + * CLI entry point for the Ossie Polaris converter. *

* Supports two modes: *

    - *
  • import: Reads from a Polaris catalog and generates an OSI YAML file
  • - *
  • export: Reads an OSI YAML file and creates tables in a Polaris catalog
  • + *
  • import: Reads from a Polaris catalog and generates an Ossie YAML file
  • + *
  • export: Reads an Ossie YAML file and creates tables in a Polaris catalog
  • *
* *
  * Usage:
- *   osi-polaris-converter import --url URL --catalog CATALOG [--client-id ID --client-secret SECRET] [-o output.yaml]
- *   osi-polaris-converter export --url URL --catalog CATALOG [--client-id ID --client-secret SECRET] <osi_model.yaml>
+ *   ossie-polaris-converter import --url URL --catalog CATALOG [--client-id ID --client-secret SECRET] [-o output.yaml]
+ *   ossie-polaris-converter export --url URL --catalog CATALOG [--client-id ID --client-secret SECRET] <osi_model.yaml>
  * 
*/ public class OsiPolarisConverter { @@ -109,7 +128,7 @@ private static void doImport(PolarisClient client, String outputFile) throws Exc if (outputFile != null) { Files.write(Paths.get(outputFile), yaml.getBytes(StandardCharsets.UTF_8)); - System.out.println("OSI model written to " + outputFile); + System.out.println("Ossie model written to " + outputFile); } else { System.out.println(yaml); } @@ -117,7 +136,7 @@ private static void doImport(PolarisClient client, String outputFile) throws Exc private static void doExport(PolarisClient client, String inputFile) throws Exception { if (inputFile == null) { - System.err.println("Error: OSI YAML file is required for export mode."); + System.err.println("Error: Ossie YAML file is required for export mode."); System.exit(1); } @@ -138,8 +157,8 @@ private static void doExport(PolarisClient client, String inputFile) throws Exce private static void printUsage() { System.err.println("Usage:"); - System.err.println(" osi-polaris-converter import --url URL --catalog CATALOG [options] [-o output.yaml]"); - System.err.println(" osi-polaris-converter export --url URL --catalog CATALOG [options] "); + System.err.println(" ossie-polaris-converter import --url URL --catalog CATALOG [options] [-o output.yaml]"); + System.err.println(" ossie-polaris-converter export --url URL --catalog CATALOG [options] "); System.err.println(); System.err.println("Options:"); System.err.println(" --url URL Polaris server URL (e.g., http://localhost:8181)"); diff --git a/converters/polaris/src/main/java/org/osi/converter/polaris/OsiYamlGenerator.java b/converters/polaris/src/main/java/org/apache/ossie/converter/polaris/OsiYamlGenerator.java similarity index 84% rename from converters/polaris/src/main/java/org/osi/converter/polaris/OsiYamlGenerator.java rename to converters/polaris/src/main/java/org/apache/ossie/converter/polaris/OsiYamlGenerator.java index 6226abb6..2533b26e 100644 --- a/converters/polaris/src/main/java/org/osi/converter/polaris/OsiYamlGenerator.java +++ b/converters/polaris/src/main/java/org/apache/ossie/converter/polaris/OsiYamlGenerator.java @@ -1,7 +1,26 @@ -package org.osi.converter.polaris; +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.ossie.converter.polaris; -import org.osi.converter.polaris.model.OsiModel; -import org.osi.converter.polaris.model.OsiModel.*; +import org.apache.ossie.converter.polaris.model.OsiModel; +import org.apache.ossie.converter.polaris.model.OsiModel.*; import java.util.ArrayList; import java.util.LinkedHashMap; @@ -9,14 +28,14 @@ import java.util.Map; /** - * Generates OSI YAML from an {@link OsiModel}. + * Generates Ossie YAML from an {@link OsiModel}. *

- * Produces well-formatted YAML that conforms to the OSI specification. + * Produces well-formatted YAML that conforms to the Ossie specification. */ public class OsiYamlGenerator { /** - * Generate OSI YAML string from a model. + * Generate Ossie YAML string from a model. */ public String generate(OsiModel model) { StringBuilder sb = new StringBuilder(); diff --git a/converters/polaris/src/main/java/org/osi/converter/polaris/PolarisClient.java b/converters/polaris/src/main/java/org/apache/ossie/converter/polaris/PolarisClient.java similarity index 90% rename from converters/polaris/src/main/java/org/osi/converter/polaris/PolarisClient.java rename to converters/polaris/src/main/java/org/apache/ossie/converter/polaris/PolarisClient.java index 39f81ff4..51334f9d 100644 --- a/converters/polaris/src/main/java/org/osi/converter/polaris/PolarisClient.java +++ b/converters/polaris/src/main/java/org/apache/ossie/converter/polaris/PolarisClient.java @@ -1,4 +1,23 @@ -package org.osi.converter.polaris; +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.ossie.converter.polaris; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; diff --git a/converters/polaris/src/main/java/org/osi/converter/polaris/PolarisExporter.java b/converters/polaris/src/main/java/org/apache/ossie/converter/polaris/PolarisExporter.java similarity index 80% rename from converters/polaris/src/main/java/org/osi/converter/polaris/PolarisExporter.java rename to converters/polaris/src/main/java/org/apache/ossie/converter/polaris/PolarisExporter.java index 0af5049d..a864cbd1 100644 --- a/converters/polaris/src/main/java/org/osi/converter/polaris/PolarisExporter.java +++ b/converters/polaris/src/main/java/org/apache/ossie/converter/polaris/PolarisExporter.java @@ -1,10 +1,29 @@ -package org.osi.converter.polaris; +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.ossie.converter.polaris; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; -import org.osi.converter.polaris.model.OsiModel; -import org.osi.converter.polaris.model.OsiModel.*; +import org.apache.ossie.converter.polaris.model.OsiModel; +import org.apache.ossie.converter.polaris.model.OsiModel.*; import java.io.IOException; import java.util.Arrays; @@ -14,10 +33,10 @@ import java.util.Map; /** - * Exports an OSI semantic model to an Apache Polaris catalog. + * Exports an Ossie semantic model to an Apache Polaris catalog. *

- * Creates namespaces and Iceberg tables in Polaris based on the OSI model's - * datasets, mapping OSI fields to Iceberg schema columns. + * Creates namespaces and Iceberg tables in Polaris based on the Ossie model's + * datasets, mapping Ossie fields to Iceberg schema columns. */ public class PolarisExporter { @@ -30,7 +49,7 @@ public PolarisExporter(PolarisClient client) { } /** - * Export the OSI model to the Polaris catalog. + * Export the Ossie model to the Polaris catalog. * Each semantic model becomes a namespace, and each dataset becomes a table. */ public void exportModel(OsiModel model) throws IOException, InterruptedException { @@ -61,7 +80,7 @@ public void exportSemanticModel(SemanticModel sm) throws IOException, Interrupte } /** - * Build an Iceberg create-table request JSON from an OSI dataset. + * Build an Iceberg create-table request JSON from an Ossie dataset. */ String buildCreateTableRequest(Dataset dataset) { ObjectNode request = objectMapper.createObjectNode(); @@ -85,7 +104,7 @@ String buildCreateTableRequest(Dataset dataset) { } /** - * Build an Iceberg schema from an OSI dataset's fields. + * Build an Iceberg schema from an Ossie dataset's fields. */ private ObjectNode buildSchema(Dataset dataset) { ObjectNode schema = objectMapper.createObjectNode(); @@ -124,9 +143,9 @@ private ObjectNode buildSchema(Dataset dataset) { } /** - * Infer an Iceberg type from an OSI field. + * Infer an Iceberg type from an Ossie field. *

- * Since OSI fields are expression-based and don't carry explicit type information, + * Since Ossie fields are expression-based and don't carry explicit type information, * we use heuristics based on field name, description, and dimension metadata. */ private String inferIcebergType(Field field) { diff --git a/converters/polaris/src/main/java/org/osi/converter/polaris/PolarisImporter.java b/converters/polaris/src/main/java/org/apache/ossie/converter/polaris/PolarisImporter.java similarity index 86% rename from converters/polaris/src/main/java/org/osi/converter/polaris/PolarisImporter.java rename to converters/polaris/src/main/java/org/apache/ossie/converter/polaris/PolarisImporter.java index 446c10e6..ef4841cc 100644 --- a/converters/polaris/src/main/java/org/osi/converter/polaris/PolarisImporter.java +++ b/converters/polaris/src/main/java/org/apache/ossie/converter/polaris/PolarisImporter.java @@ -1,8 +1,27 @@ -package org.osi.converter.polaris; +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.ossie.converter.polaris; import com.fasterxml.jackson.databind.JsonNode; -import org.osi.converter.polaris.model.OsiModel; -import org.osi.converter.polaris.model.OsiModel.*; +import org.apache.ossie.converter.polaris.model.OsiModel; +import org.apache.ossie.converter.polaris.model.OsiModel.*; import java.io.IOException; import java.util.ArrayList; @@ -10,10 +29,10 @@ import java.util.List; /** - * Imports Apache Polaris catalog metadata into an OSI semantic model. + * Imports Apache Polaris catalog metadata into an Ossie semantic model. *

* Reads namespaces and tables from a Polaris catalog via the Iceberg REST API, - * maps Iceberg table schemas to OSI datasets and fields, and produces a complete + * maps Iceberg table schemas to Ossie datasets and fields, and produces a complete * {@link OsiModel}. */ public class PolarisImporter { @@ -25,7 +44,7 @@ public PolarisImporter(PolarisClient client) { } /** - * Import all tables from all namespaces in the catalog into an OSI model. + * Import all tables from all namespaces in the catalog into an Ossie model. * Each namespace becomes a separate semantic model. */ public OsiModel importCatalog() throws IOException, InterruptedException { @@ -69,7 +88,7 @@ public SemanticModel importNamespace(List namespace) throws IOException, } /** - * Map an Iceberg table's metadata to an OSI dataset. + * Map an Iceberg table's metadata to an Ossie dataset. */ private Dataset mapTableToDataset(List namespace, String tableName, JsonNode tableMetadata) { Dataset dataset = new Dataset(); @@ -135,7 +154,7 @@ private JsonNode findCurrentSchema(JsonNode schemas, JsonNode currentSchemaId) { } /** - * Map Iceberg schema fields to OSI fields. + * Map Iceberg schema fields to Ossie fields. */ private List mapSchemaFields(JsonNode schema) { List fields = new ArrayList<>(); @@ -154,7 +173,7 @@ private List mapSchemaFields(JsonNode schema) { } /** - * Map a single Iceberg column to an OSI field. + * Map a single Iceberg column to an Ossie field. */ private Field mapColumnToField(JsonNode column) { String name = column.get("name").asText(); diff --git a/converters/polaris/src/main/java/org/osi/converter/polaris/model/OsiModel.java b/converters/polaris/src/main/java/org/apache/ossie/converter/polaris/model/OsiModel.java similarity index 87% rename from converters/polaris/src/main/java/org/osi/converter/polaris/model/OsiModel.java rename to converters/polaris/src/main/java/org/apache/ossie/converter/polaris/model/OsiModel.java index 6aeddefa..e36a5e1d 100644 --- a/converters/polaris/src/main/java/org/osi/converter/polaris/model/OsiModel.java +++ b/converters/polaris/src/main/java/org/apache/ossie/converter/polaris/model/OsiModel.java @@ -1,10 +1,29 @@ -package org.osi.converter.polaris.model; +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.ossie.converter.polaris.model; import java.util.ArrayList; import java.util.List; /** - * Java representation of an OSI semantic model parsed from YAML. + * Java representation of an Ossie semantic model parsed from YAML. */ public class OsiModel { diff --git a/converters/polaris/src/test/java/org/osi/converter/polaris/OsiPolarisConverterTest.java b/converters/polaris/src/test/java/org/apache/ossie/converter/polaris/OsiPolarisConverterTest.java similarity index 94% rename from converters/polaris/src/test/java/org/osi/converter/polaris/OsiPolarisConverterTest.java rename to converters/polaris/src/test/java/org/apache/ossie/converter/polaris/OsiPolarisConverterTest.java index a55dae63..33eba4b9 100644 --- a/converters/polaris/src/test/java/org/osi/converter/polaris/OsiPolarisConverterTest.java +++ b/converters/polaris/src/test/java/org/apache/ossie/converter/polaris/OsiPolarisConverterTest.java @@ -1,10 +1,29 @@ -package org.osi.converter.polaris; +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.ossie.converter.polaris; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.api.Test; -import org.osi.converter.polaris.model.OsiModel; -import org.osi.converter.polaris.model.OsiModel.*; +import org.apache.ossie.converter.polaris.model.OsiModel; +import org.apache.ossie.converter.polaris.model.OsiModel.*; import java.io.ByteArrayInputStream; import java.nio.charset.StandardCharsets; diff --git a/converters/salesforce/README.md b/converters/salesforce/README.md index c7eb53a4..616c8a52 100644 --- a/converters/salesforce/README.md +++ b/converters/salesforce/README.md @@ -1,8 +1,27 @@ -# OSI Salesforce Converter + + +# Apache Ossie Salesforce Converter + +A two-way converter between [Ossie semantic models](../../core-spec/spec.md) and [Salesforce Semantic Model](https://developer.salesforce.com/docs/data/semantic-layer/guide/salesforce-semantic-model-schema.html). + +This converter provides lossless, bidirectional conversion between Ossie YAML format and Salesforce Semantic Model JSON format. ## Requirements @@ -17,7 +36,7 @@ Build the executable jar from source: mvn clean package ``` -This produces a self-contained executable jar at `target/osi-salesforce-converter-0.1.0-SNAPSHOT.jar` with all dependencies bundled. +This produces a self-contained executable jar at `target/ossie-salesforce-converter-0.1.0-SNAPSHOT.jar` with all dependencies bundled. ## Setup @@ -29,9 +48,9 @@ Both schemas must be obtained and placed under `src/main/resources/schemas/` bef 2. Copy the JSON schema content from the page 3. Save it to `src/main/resources/schemas/salesforce-semantic-model-schema.json` -### OSI Schema +### Apache Ossie Schema -1. Visit the [OSI schema on GitHub](https://github.com/open-semantic-interchange/OSI/blob/main/core-spec/osi-schema.json) +1. Visit the [Ossie schema on GitHub](https://github.com/apache/ossie/blob/main/core-spec/osi-schema.json) 2. Copy the raw JSON contents 3. Save it to `src/main/resources/schemas/osi-schema.json` @@ -39,36 +58,36 @@ Both schemas must be obtained and placed under `src/main/resources/schemas/` bef ### Command Line -#### Import (Salesforce → OSI) +#### Import (Salesforce → Apache Ossie) -Convert a Salesforce Semantic Model JSON file to OSI YAML format: +Convert a Salesforce Semantic Model JSON file to Ossie YAML format: ```bash -java -jar target/osi-salesforce-converter-0.1.0-SNAPSHOT.jar toOSI input.json +java -jar target/ossie-salesforce-converter-0.1.0-SNAPSHOT.jar toOSI input.json # Output: Customer_Orders_Model.yaml (named after model's 'name' field) # Created in the same directory as the input file ``` Example: ```bash -java -jar target/osi-salesforce-converter-0.1.0-SNAPSHOT.jar toOSI \ +java -jar target/ossie-salesforce-converter-0.1.0-SNAPSHOT.jar toOSI \ src/test/resources/examples/salesforceToOsi.json # Output: src/test/resources/examples/Customer_Orders_Model.yaml ``` -#### Export (OSI → Salesforce) +#### Export (Apache Ossie → Salesforce) -Convert an OSI YAML file to Salesforce Semantic Model JSON format: +Convert an Ossie YAML file to Salesforce Semantic Model JSON format: ```bash -java -jar target/osi-salesforce-converter-0.1.0-SNAPSHOT.jar toSF input.yaml +java -jar target/ossie-salesforce-converter-0.1.0-SNAPSHOT.jar toSF input.yaml # Output: Customer_Orders_Model.json (named after model's 'apiName' field) # Created in the same directory as the input file ``` Example: ```bash -java -jar target/osi-salesforce-converter-0.1.0-SNAPSHOT.jar toSF \ +java -jar target/ossie-salesforce-converter-0.1.0-SNAPSHOT.jar toSF \ src/test/resources/examples/osiToSalesforce.yaml # Output: src/test/resources/examples/Customer_Orders_Model.json ``` @@ -78,9 +97,9 @@ java -jar target/osi-salesforce-converter-0.1.0-SNAPSHOT.jar toSF \ #### String Conversion ```java -import org.osi.converter.Converter; -import org.osi.converter.ConverterFactory; -import org.osi.converter.ConversionDirection; +import org.apache.ossie.converter.Converter; +import org.apache.ossie.converter.ConverterFactory; +import org.apache.ossie.converter.ConversionDirection; Converter sfToOsi = ConverterFactory.getConverter(ConversionDirection.SALESFORCE_TO_OSI); List osiYamlList = sfToOsi.convert(salesforceJsonString); @@ -93,9 +112,9 @@ List salesforceJsonList = osiToSf.convert(osiYamlString); #### File Conversion ```java -import org.osi.converter.Converter; -import org.osi.converter.ConverterFactory; -import org.osi.converter.ConversionDirection; +import org.apache.ossie.converter.Converter; +import org.apache.ossie.converter.ConverterFactory; +import org.apache.ossie.converter.ConversionDirection; import java.nio.file.Paths; @@ -111,13 +130,13 @@ osiToSf.convert(Paths.get("input/model.yaml"), Paths.get("output/")); - **Schema-validated** - Input is validated against JSON Schema before processing - **Lossless conversion** - Unmapped properties are preserved in `custom_extensions` - **Bidirectional** - Full bi-directional conversion without data loss -- **Supports OSI Specification v0.2.0.dev0** +- **Supports Ossie Specification v0.2.0.dev0** ## Mapping Reference -### Import (Salesforce → OSI) +### Import (Salesforce → Apache Ossie) -| Salesforce | OSI | +| Salesforce | Ossie | |------------|-----| | `apiName` | `name` | | `semanticDataObjects[]` | `datasets[]` | @@ -132,9 +151,9 @@ osiToSf.convert(Paths.get("input/model.yaml"), Paths.get("output/")); | `businessPreferences` | `ai_context` | | Unmapped properties | `custom_extensions` (vendor: `SALESFORCE`) | -### Export (OSI → Salesforce) +### Export (Apache Ossie → Salesforce) -| OSI | Salesforce | +| Ossie | Salesforce | |-----|------------| | `name` | `apiName` | | `datasets[]` | `semanticDataObjects[]` | @@ -158,7 +177,7 @@ Fields are automatically classified as dimensions or measurements based on expre ### Relationship Handling -**Unsupported relationships** (containing Formula or SemanticField types) are stored in `custom_extensions` at the model level rather than being converted to OSI relationships. +**Unsupported relationships** (containing Formula or SemanticField types) are stored in `custom_extensions` at the model level rather than being converted to Ossie relationships. ## Architecture @@ -205,16 +224,16 @@ Fields are automatically classified as dimensions or measurements based on expre **GenericMappingEngine** — Path-based property mapping using `mappings.yaml` configuration -**CustomExtensionHandler** — Preserves unmapped Salesforce properties in OSI's `custom_extensions` for lossless bi-directional conversion +**CustomExtensionHandler** — Preserves unmapped Salesforce properties in Ossie's `custom_extensions` for lossless bi-directional conversion **SchemaValidator** — Validates input against JSON schemas before conversion ## Examples See the test suite for sample models demonstrating various features: -- `src/test/resources/examples/osiToSalesforce.yaml` - OSI model example -- `src/test/java/org/osi/OsiToSalesforceConverterTest.java` - OSI to Salesforce conversion tests -- `src/test/java/org/osi/SalesforceToOsiConverterTest.java` - Salesforce to OSI conversion tests +- `src/test/resources/examples/osiToSalesforce.yaml` - Ossie model example +- `src/test/java/org/apache/ossie/OsiToSalesforceConverterTest.java` - Ossie to Salesforce conversion tests +- `src/test/java/org/apache/ossie/SalesforceToOsiConverterTest.java` - Salesforce to Ossie conversion tests ## License diff --git a/converters/salesforce/pom.xml b/converters/salesforce/pom.xml index 9fcaa9e0..8e782ca2 100644 --- a/converters/salesforce/pom.xml +++ b/converters/salesforce/pom.xml @@ -1,16 +1,42 @@ + + 4.0.0 - org.osi - osi-salesforce-converter + + org.apache + apache + 39 + + + + org.apache.ossie + ossie-salesforce-converter 0.1.0-SNAPSHOT jar - OSI Salesforce Converter - OSI Salesforce bidirectional semantic model converter + Apache Ossie Salesforce Converter + Apache Ossie Salesforce bidirectional semantic model converter 17 @@ -71,7 +97,7 @@ + + org.apache.rat + apache-rat-plugin + + + verify + + check + + + + + true + + **/target/** + + **/*.json + + + diff --git a/converters/salesforce/src/main/java/org/osi/app/OsiSalesforceConverter.java b/converters/salesforce/src/main/java/org/apache/ossie/app/OsiSalesforceConverter.java similarity index 61% rename from converters/salesforce/src/main/java/org/osi/app/OsiSalesforceConverter.java rename to converters/salesforce/src/main/java/org/apache/ossie/app/OsiSalesforceConverter.java index 2b0fea23..3d80e32c 100644 --- a/converters/salesforce/src/main/java/org/osi/app/OsiSalesforceConverter.java +++ b/converters/salesforce/src/main/java/org/apache/ossie/app/OsiSalesforceConverter.java @@ -1,18 +1,37 @@ -package org.osi.app; +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.ossie.app; -import org.osi.converter.Converter; -import org.osi.converter.ConverterFactory; -import org.osi.converter.ConversionDirection; -import org.osi.exception.ConversionException; -import org.osi.exception.InvalidInputException; +import org.apache.ossie.converter.Converter; +import org.apache.ossie.converter.ConverterFactory; +import org.apache.ossie.converter.ConversionDirection; +import org.apache.ossie.exception.ConversionException; +import org.apache.ossie.exception.InvalidInputException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; /** - * Main application class for the OSI-Salesforce Converter. + * Main application class for the Ossie-Salesforce Converter. * - *

Converts between Salesforce Semantic Model and OSI formats. + *

Converts between Salesforce Semantic Model and Ossie formats. * Output file is placed in the same directory as input with the appropriate extension.

* */ diff --git a/converters/salesforce/src/main/java/org/osi/converter/AbstractConverter.java b/converters/salesforce/src/main/java/org/apache/ossie/converter/AbstractConverter.java similarity index 86% rename from converters/salesforce/src/main/java/org/osi/converter/AbstractConverter.java rename to converters/salesforce/src/main/java/org/apache/ossie/converter/AbstractConverter.java index dd18967f..88a51804 100644 --- a/converters/salesforce/src/main/java/org/osi/converter/AbstractConverter.java +++ b/converters/salesforce/src/main/java/org/apache/ossie/converter/AbstractConverter.java @@ -1,4 +1,23 @@ -package org.osi.converter; +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.ossie.converter; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; @@ -6,10 +25,10 @@ import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator; -import org.osi.exception.ConversionException; -import org.osi.exception.InvalidInputException; -import org.osi.mapper.FileBasedPropertyMapper; -import org.osi.mapper.PropertyMapper; +import org.apache.ossie.exception.ConversionException; +import org.apache.ossie.exception.InvalidInputException; +import org.apache.ossie.mapper.FileBasedPropertyMapper; +import org.apache.ossie.mapper.PropertyMapper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/converters/salesforce/src/main/java/org/apache/ossie/converter/ConversionDirection.java b/converters/salesforce/src/main/java/org/apache/ossie/converter/ConversionDirection.java new file mode 100644 index 00000000..aa507d78 --- /dev/null +++ b/converters/salesforce/src/main/java/org/apache/ossie/converter/ConversionDirection.java @@ -0,0 +1,49 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.ossie.converter; + +/** + * Enum representing the direction of conversion. + * + */ +public enum ConversionDirection { + /** + * Converting from Ossie YAML format to Salesforce JSON format. + */ + OSI_TO_SALESFORCE, + + /** + * Converting from Salesforce JSON format to Ossie YAML format. + */ + SALESFORCE_TO_OSI; + + /** + * Converts enum name to pipeline configuration key. + * Maps OSI_TO_SALESFORCE -> "osiToSalesforce" and SALESFORCE_TO_OSI -> "salesforceToOsi" + * + * @return The pipeline key used in YAML configuration + */ + public String toPipelineKey() { + return switch (this) { + case OSI_TO_SALESFORCE -> "osiToSalesforce"; + case SALESFORCE_TO_OSI -> "salesforceToOsi"; + }; + } +} diff --git a/converters/salesforce/src/main/java/org/apache/ossie/converter/Converter.java b/converters/salesforce/src/main/java/org/apache/ossie/converter/Converter.java new file mode 100644 index 00000000..6554643c --- /dev/null +++ b/converters/salesforce/src/main/java/org/apache/ossie/converter/Converter.java @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.ossie.converter; + +import java.nio.file.Path; +import java.util.List; + +/** + * Interface for converting between data formats (YAML and JSON). + * + *

This is the external API for conversion. Implementations handle the conversion + * of data from one format to another, with support for property mapping.

+ * + */ +public interface Converter { + + /** + * Converts the input file and writes results to the specified output directory. + * + *

Each semantic model is written to a separate file named after its apiName. + * Example: "Sales_Model.json", "Marketing_Model.json" + * + * @param inputPath the path to the input file + * @param outputDir the directory where output files will be written + */ + void convert(Path inputPath, Path outputDir); + + /** + * Converts string content from the source format to the target format. + * + *

For Ossie to Salesforce: returns one Salesforce model per Ossie semantic_model entry. + *

For Salesforce to Ossie: returns one Ossie document with one semantic_model entry. + * + * @param content the content to convert + * @return list of converted content strings (one per semantic model) + */ + List convert(String content); +} diff --git a/converters/salesforce/src/main/java/org/osi/converter/ConverterConstants.java b/converters/salesforce/src/main/java/org/apache/ossie/converter/ConverterConstants.java similarity index 84% rename from converters/salesforce/src/main/java/org/osi/converter/ConverterConstants.java rename to converters/salesforce/src/main/java/org/apache/ossie/converter/ConverterConstants.java index 067530ce..19d68cce 100644 --- a/converters/salesforce/src/main/java/org/osi/converter/ConverterConstants.java +++ b/converters/salesforce/src/main/java/org/apache/ossie/converter/ConverterConstants.java @@ -1,4 +1,23 @@ -package org.osi.converter; +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.ossie.converter; /** * Constants used across converters for property names and structure keys. @@ -18,12 +37,12 @@ public enum Level { METRICS } - // OSI root structure + // Ossie root structure public static final String VERSION = "version"; public static final String OSI_VERSION = "0.2.0.dev0"; public static final String SEMANTIC_MODEL = "semantic_model"; - // OSI semantic model structure + // Ossie semantic model structure public static final String CUSTOM_EXTENSIONS = "custom_extensions"; public static final String DATASETS = "datasets"; public static final String FIELDS = "fields"; diff --git a/converters/salesforce/src/main/java/org/apache/ossie/converter/ConverterFactory.java b/converters/salesforce/src/main/java/org/apache/ossie/converter/ConverterFactory.java new file mode 100644 index 00000000..657e9504 --- /dev/null +++ b/converters/salesforce/src/main/java/org/apache/ossie/converter/ConverterFactory.java @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.ossie.converter; + +/** + * Factory for creating converters based on conversion direction. + * + */ +public class ConverterFactory { + + /** + * Creates a converter for the specified direction. + * + * @param direction The conversion direction + * @return A Converter instance configured for the specified direction + */ + public static Converter getConverter(ConversionDirection direction) { + return new ConverterImpl(direction); + } +} diff --git a/converters/salesforce/src/main/java/org/osi/converter/ConverterImpl.java b/converters/salesforce/src/main/java/org/apache/ossie/converter/ConverterImpl.java similarity index 80% rename from converters/salesforce/src/main/java/org/osi/converter/ConverterImpl.java rename to converters/salesforce/src/main/java/org/apache/ossie/converter/ConverterImpl.java index 722dc2b1..a6b5cff6 100644 --- a/converters/salesforce/src/main/java/org/osi/converter/ConverterImpl.java +++ b/converters/salesforce/src/main/java/org/apache/ossie/converter/ConverterImpl.java @@ -1,15 +1,34 @@ -package org.osi.converter; +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.ossie.converter; -import static org.osi.converter.ConverterConstants.*; -import static org.osi.util.DataStructureUtils.*; +import static org.apache.ossie.converter.ConverterConstants.*; +import static org.apache.ossie.util.DataStructureUtils.*; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; -import org.osi.converter.pipeline.*; -import org.osi.converter.pipeline.*; -import org.osi.exception.ConversionException; -import org.osi.validator.SchemaValidator; +import org.apache.ossie.converter.pipeline.*; +import org.apache.ossie.converter.pipeline.*; +import org.apache.ossie.exception.ConversionException; +import org.apache.ossie.validator.SchemaValidator; import java.util.ArrayList; import java.util.LinkedHashMap; @@ -92,7 +111,7 @@ private List convertOsiToSalesforce(Map osiRoot) { private List convertSalesforceToOsi(Map sourceData) { String result = executePipeline(sourceData); - // Wrap output in OSI root structure + // Wrap output in Ossie root structure try { Map outputData = yamlMapper.readValue(result, new TypeReference<>() {}); Map osiRoot = new LinkedHashMap<>(); @@ -100,7 +119,7 @@ private List convertSalesforceToOsi(Map sourceData) { osiRoot.put(SEMANTIC_MODEL, List.of(outputData)); return List.of(toYaml(osiRoot)); } catch (JsonProcessingException e) { - throw new ConversionException("Failed to wrap output in OSI root", e); + throw new ConversionException("Failed to wrap output in Ossie root", e); } } @@ -137,7 +156,7 @@ protected String extractModelName(String result) { String field = directionConfig.getExtractModelNameFrom(); - // Handle OSI format (wrapped in semantic_model array) + // Handle Ossie format (wrapped in semantic_model array) if (direction == ConversionDirection.SALESFORCE_TO_OSI) { List models = getList(data, SEMANTIC_MODEL); if (models != null && !models.isEmpty()) { diff --git a/converters/salesforce/src/main/java/org/osi/converter/CustomExtensionHandler.java b/converters/salesforce/src/main/java/org/apache/ossie/converter/CustomExtensionHandler.java similarity index 86% rename from converters/salesforce/src/main/java/org/osi/converter/CustomExtensionHandler.java rename to converters/salesforce/src/main/java/org/apache/ossie/converter/CustomExtensionHandler.java index aaf16d73..4ac03de5 100644 --- a/converters/salesforce/src/main/java/org/osi/converter/CustomExtensionHandler.java +++ b/converters/salesforce/src/main/java/org/apache/ossie/converter/CustomExtensionHandler.java @@ -1,12 +1,31 @@ -package org.osi.converter; +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.ossie.converter; -import static org.osi.converter.ConverterConstants.*; -import static org.osi.util.DataStructureUtils.*; +import static org.apache.ossie.converter.ConverterConstants.*; +import static org.apache.ossie.util.DataStructureUtils.*; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; -import org.osi.converter.ConverterConstants.Level; -import org.osi.util.PathUtils; +import org.apache.ossie.converter.ConverterConstants.Level; +import org.apache.ossie.util.PathUtils; import java.util.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -14,8 +33,8 @@ /** * Handler for adding and restoring custom_extensions in the Semantic Model. * - *

Handles identifying unmapped properties during Salesforce-to-OSI conversion - * and restoring them during OSI-to-Salesforce conversion.

+ *

Handles identifying unmapped properties during Salesforce-to-Ossie conversion + * and restoring them during Ossie-to-Salesforce conversion.

* */ public class CustomExtensionHandler { @@ -32,7 +51,7 @@ public CustomExtensionHandler(ObjectMapper jsonMapper) { * Unified method to restore custom_extensions at any level. * * @param outputData The output data containing semanticModel - * @param sourceData The source OSI data + * @param sourceData The source Ossie data * @param level The level to restore */ public void restoreCustomExtensionsAtLevel( @@ -64,7 +83,7 @@ public void restoreCustomExtensionsAtLevel( * Generic method that works for datasets, relationships, etc. * * @param semanticModel The semanticModel object - * @param sourceData The source OSI data + * @param sourceData The source Ossie data * @param sourceArrayKey The key in source data (e.g., "datasets", "relationships") * @param targetArrayKey The key in semanticModel (e.g., "semanticDataObjects", "semanticRelationships") * @param sourceIdKey The identifying key in source items (e.g., "name") @@ -128,7 +147,7 @@ private boolean hasSalesforceCustomExtension(Map item) { * Unified method to store unmapped properties as custom_extensions at any level. * This is the inverse of restoreCustomExtensionsAtLevel. * - * @param outputData The output OSI data + * @param outputData The output Ossie data * @param sourceData The source Salesforce data * @param allHandledProps All properties that were handled (mapped + programmatic) * @param level The level to store @@ -167,12 +186,12 @@ public void storeUnmappedProperties( /** * Stores unmapped properties for array items (datasets, relationships, metrics). * - * @param osiData The OSI output data + * @param osiData The Ossie output data * @param sfData The Salesforce source data * @param allHandledProps All properties that were handled (from mappings + programmatic) - * @param osiArrayKey The OSI array key (e.g., "datasets") + * @param osiArrayKey The Ossie array key (e.g., "datasets") * @param sfArrayKey The SF array key (e.g., "semanticDataObjects") - * @param osiIdKey The OSI identifier key (e.g., "name") + * @param osiIdKey The Ossie identifier key (e.g., "name") * @param sfIdKey The SF identifier key (e.g., "apiName") */ private void storeArrayLevelUnmappedProperties( @@ -194,7 +213,7 @@ private void storeArrayLevelUnmappedProperties( return; } - // Process each OSI item and find its matching SF item + // Process each Ossie item and find its matching SF item streamMaps(osiArray).forEach(osiItem -> { // Find matching SF item by identifier String itemId = getString(osiItem, osiIdKey); @@ -220,10 +239,10 @@ private void storeArrayLevelUnmappedProperties( } /** - * Stores unmapped properties from a source item into an OSI item's custom_extensions. + * Stores unmapped properties from a source item into an Ossie item's custom_extensions. * Generic method that can be used for fields or any other individual items. * - * @param osiItem The target OSI item to add custom_extensions to + * @param osiItem The target Ossie item to add custom_extensions to * @param sfItem The source Salesforce item * @param handledProps Set of property keys that were handled (mapped or programmatically processed) */ @@ -280,11 +299,11 @@ public void addCustomExtension(Map target, Map c } /** - * Extracts SALESFORCE vendor custom_extension from OSI item and merges into SF item. + * Extracts SALESFORCE vendor custom_extension from Ossie item and merges into SF item. * This is a generic method that can be used for any level (dataset, field, relationship, metric). * * @param sfItem The Salesforce item to merge properties into - * @param osiItem The OSI item containing custom_extensions + * @param osiItem The Ossie item containing custom_extensions */ public void restoreSalesforceCustomExtension(Map sfItem, Map osiItem) { Object customExtensionsObj = osiItem.get(CUSTOM_EXTENSIONS); diff --git a/converters/salesforce/src/main/java/org/osi/converter/DatasetMappingHandler.java b/converters/salesforce/src/main/java/org/apache/ossie/converter/DatasetMappingHandler.java similarity index 74% rename from converters/salesforce/src/main/java/org/osi/converter/DatasetMappingHandler.java rename to converters/salesforce/src/main/java/org/apache/ossie/converter/DatasetMappingHandler.java index 94517dac..5163f5db 100644 --- a/converters/salesforce/src/main/java/org/osi/converter/DatasetMappingHandler.java +++ b/converters/salesforce/src/main/java/org/apache/ossie/converter/DatasetMappingHandler.java @@ -1,24 +1,43 @@ -package org.osi.converter; +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.ossie.converter; -import static org.osi.converter.ConverterConstants.*; -import static org.osi.util.DataStructureUtils.*; +import static org.apache.ossie.converter.ConverterConstants.*; +import static org.apache.ossie.util.DataStructureUtils.*; -import org.osi.converter.ConverterConstants.Level; -import org.osi.converter.pipeline.PipelineStep; +import org.apache.ossie.converter.ConverterConstants.Level; +import org.apache.ossie.converter.pipeline.PipelineStep; import java.util.*; import java.util.stream.Collectors; -import org.osi.util.MappingUtils; +import org.apache.ossie.util.MappingUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * Bidirectional handler for mapping datasets between OSI and Salesforce formats. + * Bidirectional handler for mapping datasets between Ossie and Salesforce formats. * *

Supports both conversion directions: *

    - *
  • OSI → Salesforce: datasets → semanticDataObjects, apply SF defaults, restore custom_extensions
  • - *
  • Salesforce → OSI: semanticDataObjects → datasets, store unmapped properties in custom_extensions
  • + *
  • Ossie → Salesforce: datasets → semanticDataObjects, apply SF defaults, restore custom_extensions
  • + *
  • Salesforce → Ossie: semanticDataObjects → datasets, store unmapped properties in custom_extensions
  • *
* */ @@ -45,7 +64,7 @@ public void execute(Map sourceData, Map outputDa } /** - * Maps OSI datasets to Salesforce semanticDataObjects. + * Maps Ossie datasets to Salesforce semanticDataObjects. */ private void mapOsiToSalesforce( Map sourceData, Map outputData, Map mappings) { @@ -64,7 +83,7 @@ private void mapOsiToSalesforce( } /** - * Maps Salesforce semanticDataObjects to OSI datasets. + * Maps Salesforce semanticDataObjects to Ossie datasets. */ private void mapSalesforceToOsi( Map sourceData, Map outputData, Map mappings) { @@ -112,7 +131,7 @@ private void mapSalesforceToOsi( /** * Applies default values for required Salesforce data object properties. - * Used when converting OSI → Salesforce. + * Used when converting Ossie → Salesforce. */ private void applyDefaults(List dataObjects) { for (Object dataObjectObj : dataObjects) { @@ -129,7 +148,7 @@ private void applyDefaults(List dataObjects) { /** * Stores shared entities in semanticDataObjects in top-level custom_extensions. * - * @param outputData The output OSI data + * @param outputData The output Ossie data * @param sharedEntities List of non-standard semanticDataObjects */ private void storeSharedEntitiesInCustomExtensions( diff --git a/converters/salesforce/src/main/java/org/osi/converter/FieldMappingHandler.java b/converters/salesforce/src/main/java/org/apache/ossie/converter/FieldMappingHandler.java similarity index 90% rename from converters/salesforce/src/main/java/org/osi/converter/FieldMappingHandler.java rename to converters/salesforce/src/main/java/org/apache/ossie/converter/FieldMappingHandler.java index 01ccfc96..1d148342 100644 --- a/converters/salesforce/src/main/java/org/osi/converter/FieldMappingHandler.java +++ b/converters/salesforce/src/main/java/org/apache/ossie/converter/FieldMappingHandler.java @@ -1,27 +1,46 @@ -package org.osi.converter; +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.ossie.converter; -import org.osi.converter.pipeline.PipelineStep; +import org.apache.ossie.converter.pipeline.PipelineStep; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.*; import java.util.regex.Pattern; -import static org.osi.converter.ConverterConstants.*; -import static org.osi.util.DataStructureUtils.*; +import static org.apache.ossie.converter.ConverterConstants.*; +import static org.apache.ossie.util.DataStructureUtils.*; /** - * Bidirectional handler for mapping fields between OSI and Salesforce formats. + * Bidirectional handler for mapping fields between Ossie and Salesforce formats. * - *

OSI → SF: Maps dataset fields to SemanticDimensions and SemanticMeasurements - *

SF → OSI: Maps SemanticDimensions and SemanticMeasurements to dataset fields + *

Ossie → SF: Maps dataset fields to SemanticDimensions and SemanticMeasurements + *

SF → Ossie: Maps SemanticDimensions and SemanticMeasurements to dataset fields * */ public class FieldMappingHandler implements PipelineStep { private static final Logger logger = LoggerFactory.getLogger(FieldMappingHandler.class); - // Properties handled when converting SF dimensions/measurements to OSI fields + // Properties handled when converting SF dimensions/measurements to Ossie fields private static final Set SF_FIELD_HANDLED_PROPS = Set.of(API_NAME, LABEL, DESCRIPTION, DATA_OBJECT_FIELD_NAME); @@ -65,10 +84,10 @@ public void execute(Map sourceData, Map outputDa } /** - * Maps OSI dataset fields to Salesforce SemanticDimensions and SemanticMeasurements. + * Maps Ossie dataset fields to Salesforce SemanticDimensions and SemanticMeasurements. * * @param outputData The output map containing semanticModel - * @param sourceData The source OSI data + * @param sourceData The source Ossie data */ private void mapOsiToSalesforce( Map sourceData, Map outputData) { @@ -92,7 +111,7 @@ private void mapOsiToSalesforce( } /** - * Maps Salesforce SemanticDimensions and SemanticMeasurements to OSI dataset fields. + * Maps Salesforce SemanticDimensions and SemanticMeasurements to Ossie dataset fields. */ private void mapSalesforceToOsi( Map sourceData, Map outputData) { @@ -110,11 +129,11 @@ private void mapSalesforceToOsi( String apiName = getString(sfDataObject, API_NAME); if (apiName == null) continue; - // Find matching OSI dataset by name (mapped from apiName) + // Find matching Ossie dataset by name (mapped from apiName) Map osiDataset = findItemById(osiDatasets, NAME, apiName); if (osiDataset == null) continue; - // Convert SF dimensions and measurements to OSI fields + // Convert SF dimensions and measurements to Ossie fields convertSalesforceFieldsToOsi(sfDataObject, osiDataset); } @@ -126,13 +145,13 @@ private void mapSalesforceToOsi( } /** - * Converts Salesforce dimensions and measurements to OSI fields for a dataset. + * Converts Salesforce dimensions and measurements to Ossie fields for a dataset. */ private void convertSalesforceFieldsToOsi( Map sfDataObject, Map osiDataset) { List osiFields = getOrCreateList(osiDataset, FIELDS); - // Process semanticDimensions → OSI fields + // Process semanticDimensions → Ossie fields List sfDimensions = getList(sfDataObject, SEMANTIC_DIMENSIONS); if (sfDimensions != null) { for (Object sfDimObj : sfDimensions) { @@ -142,7 +161,7 @@ private void convertSalesforceFieldsToOsi( } } - // Process semanticMeasurements → OSI fields + // Process semanticMeasurements → Ossie fields List sfMeasurements = getList(sfDataObject, SEMANTIC_MEASUREMENTS); if (sfMeasurements != null) { for (Object sfMeasObj : sfMeasurements) { @@ -154,7 +173,7 @@ private void convertSalesforceFieldsToOsi( } /** - * Converts a Salesforce dimension to an OSI field with dimension property. + * Converts a Salesforce dimension to an Ossie field with dimension property. */ private Map convertDimensionToOsiField(Map sfDimension) { Map osiField = new LinkedHashMap<>(); @@ -184,7 +203,7 @@ private Map convertDimensionToOsiField(Map sfDim } /** - * Converts a Salesforce measurement to an OSI field without dimension property. + * Converts a Salesforce measurement to an Ossie field without dimension property. */ private Map convertMeasurementToOsiField(Map sfMeasurement) { Map osiField = new LinkedHashMap<>(); @@ -203,7 +222,7 @@ private Map convertMeasurementToOsiField(Map sfM } /** - * Maps common field properties from Salesforce to OSI format. + * Maps common field properties from Salesforce to Ossie format. * Common properties: name (from apiName), label, description. */ private void mapCommonFieldProperties(Map sfField, Map osiField) { @@ -222,7 +241,7 @@ private void mapCommonFieldProperties(Map sfField, Map wrapExpression(String expressionValue) { @@ -250,7 +269,7 @@ private Map wrapExpression(String expressionValue) { * CalculatedN/AMODEL.semanticCalculatedDimensions * * - * @param osiDataset The OSI dataset + * @param osiDataset The Ossie dataset * @param sfDataObject The Salesforce data object to add direct fields to * @param outputData The Salesforce model for adding calculated dimensions */ @@ -267,7 +286,7 @@ private void processFieldsForDataset( for (Object osiFieldObj : osiFields) { Map osiField = asMap(osiFieldObj); - // Determine field type based on OSI structure + // Determine field type based on Ossie structure boolean hasDimension = osiField.containsKey(DIMENSION); ExpressionInfo expressionInfo = unwrapExpression(osiField); @@ -316,7 +335,7 @@ private void processFieldsForDataset( * Maps field properties based on whether the field is calculated. * Includes common properties plus type-specific properties. * - * @param osiField The OSI field + * @param osiField The Ossie field * @param expression The extracted expression string * @return A map with Salesforce field properties */ @@ -343,10 +362,10 @@ private Map mapFieldProperties( } /** - * Creates a Salesforce semanticCalculatedDimension from an OSI field with a calculated expression. + * Creates a Salesforce semanticCalculatedDimension from an Ossie field with a calculated expression. * Per schema: required properties are apiName and expression. * - * @param osiField The OSI field + * @param osiField The Ossie field * @param expression The calculated expression * @return A map representing a semanticCalculatedDimension */ @@ -422,10 +441,10 @@ private record RoutingResult(List dataObjectDimensions, List dat private record ExpressionInfo(String expression, String dialect) {} /** - * Extracts the expression value and dialect from OSI field's expression.dialects[0].expression. + * Extracts the expression value and dialect from Ossie field's expression.dialects[0].expression. * This unwraps the nested structure to get the simple column reference and its dialect. * - * @param osiField The OSI field containing expression structure + * @param osiField The Ossie field containing expression structure * @return ExpressionInfo containing the expression string and dialect type, or null if not found */ private ExpressionInfo unwrapExpression(Map osiField) { @@ -532,7 +551,7 @@ private void applyFieldDefaults(Map sfField) { * * * @param sourceData The source Salesforce data (will be modified to remove converted dimensions) - * @param outputData The output OSI data containing datasets + * @param outputData The output Ossie data containing datasets */ private void processModelLevelCalculatedDimensions( Map sourceData, Map outputData) { @@ -615,11 +634,11 @@ private String getSingleDataObjectFromDependencies(List dependencies) { } /** - * Converts a Salesforce semanticCalculatedDimension to an OSI field with dimension property. + * Converts a Salesforce semanticCalculatedDimension to an Ossie field with dimension property. * Similar to convertDimensionToOsiField but handles expression (not dataObjectFieldName). * * @param calcDim The Salesforce calculated dimension - * @return An OSI field map with dimension property and wrapped expression + * @return An Ossie field map with dimension property and wrapped expression */ private Map convertCalculatedDimensionToField(Map calcDim) { Map osiField = new LinkedHashMap<>(); diff --git a/converters/salesforce/src/main/java/org/osi/converter/GenericMappingEngine.java b/converters/salesforce/src/main/java/org/apache/ossie/converter/GenericMappingEngine.java similarity index 82% rename from converters/salesforce/src/main/java/org/osi/converter/GenericMappingEngine.java rename to converters/salesforce/src/main/java/org/apache/ossie/converter/GenericMappingEngine.java index 86abc7df..2c71b6ed 100644 --- a/converters/salesforce/src/main/java/org/osi/converter/GenericMappingEngine.java +++ b/converters/salesforce/src/main/java/org/apache/ossie/converter/GenericMappingEngine.java @@ -1,9 +1,28 @@ -package org.osi.converter; +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.ossie.converter; -import static org.osi.util.DataStructureUtils.*; +import static org.apache.ossie.util.DataStructureUtils.*; -import org.osi.util.MappingUtils; -import org.osi.util.PathUtils; +import org.apache.ossie.util.MappingUtils; +import org.apache.ossie.util.PathUtils; import java.util.LinkedHashMap; import java.util.List; diff --git a/converters/salesforce/src/main/java/org/osi/converter/MetricMappingHandler.java b/converters/salesforce/src/main/java/org/apache/ossie/converter/MetricMappingHandler.java similarity index 70% rename from converters/salesforce/src/main/java/org/osi/converter/MetricMappingHandler.java rename to converters/salesforce/src/main/java/org/apache/ossie/converter/MetricMappingHandler.java index 4d1a9da8..c54d46a9 100644 --- a/converters/salesforce/src/main/java/org/osi/converter/MetricMappingHandler.java +++ b/converters/salesforce/src/main/java/org/apache/ossie/converter/MetricMappingHandler.java @@ -1,23 +1,42 @@ -package org.osi.converter; +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.ossie.converter; -import static org.osi.converter.ConverterConstants.*; -import static org.osi.util.DataStructureUtils.*; +import static org.apache.ossie.converter.ConverterConstants.*; +import static org.apache.ossie.util.DataStructureUtils.*; -import org.osi.converter.ConverterConstants.Level; -import org.osi.converter.pipeline.PipelineStep; +import org.apache.ossie.converter.ConverterConstants.Level; +import org.apache.ossie.converter.pipeline.PipelineStep; import java.util.*; -import org.osi.util.MappingUtils; +import org.apache.ossie.util.MappingUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * Bidirectional handler for mapping metrics between OSI and Salesforce formats. + * Bidirectional handler for mapping metrics between Ossie and Salesforce formats. * *

Supports both conversion directions: *

    - *
  • OSI → Salesforce: unwrap expression from dialects structure
  • - *
  • Salesforce → OSI: wrap expression in dialects structure
  • + *
  • Ossie → Salesforce: unwrap expression from dialects structure
  • + *
  • Salesforce → Ossie: wrap expression in dialects structure
  • *
* */ @@ -47,7 +66,7 @@ public void execute(Map sourceData, Map outputDa } /** - * Maps OSI metrics to Salesforce semanticCalculatedMeasurements. + * Maps Ossie metrics to Salesforce semanticCalculatedMeasurements. */ private void mapOsiToSalesforce( Map sourceData, Map outputData, Map mappings) { @@ -61,11 +80,11 @@ private void mapOsiToSalesforce( Map metricMappings = MappingUtils.filterMappingsByPrefix(mappings, METRICS); metricMappings.keySet().forEach(mappings::remove); - logger.debug("Metrics are not mapped in OSI to Salesforce direction"); + logger.debug("Metrics are not mapped in Ossie to Salesforce direction"); } /** - * Maps Salesforce semanticCalculatedMeasurements to OSI metrics. + * Maps Salesforce semanticCalculatedMeasurements to Ossie metrics. */ private void mapSalesforceToOsi( Map sourceData, Map outputData, Map mappings) { @@ -100,7 +119,7 @@ private void mapSalesforceToOsi( /** - * Wraps expressions for SF→OSI conversion. + * Wraps expressions for SF→Ossie conversion. */ private void wrapExpressions(List sfMetrics, List osiMetrics) { for (int i = 0; i < sfMetrics.size() && i < osiMetrics.size(); i++) { @@ -110,14 +129,14 @@ private void wrapExpressions(List sfMetrics, List osiMetrics) { // Get expression from SF metric String expressionValue = getString(sfMetric, EXPRESSION); if (expressionValue != null) { - // Wrap in OSI dialect structure + // Wrap in Ossie dialect structure osiMetric.put(EXPRESSION, wrapExpression(expressionValue)); } } } /** - * Wraps a simple expression string in OSI's expression.dialects structure. + * Wraps a simple expression string in Ossie's expression.dialects structure. * Tags expressions with TABLEAU dialect as they come from Salesforce (Tableau CRM). */ private Map wrapExpression(String expressionValue) { diff --git a/converters/salesforce/src/main/java/org/osi/converter/RelationshipMappingHandler.java b/converters/salesforce/src/main/java/org/apache/ossie/converter/RelationshipMappingHandler.java similarity index 90% rename from converters/salesforce/src/main/java/org/osi/converter/RelationshipMappingHandler.java rename to converters/salesforce/src/main/java/org/apache/ossie/converter/RelationshipMappingHandler.java index cd669a65..92f67d0c 100644 --- a/converters/salesforce/src/main/java/org/osi/converter/RelationshipMappingHandler.java +++ b/converters/salesforce/src/main/java/org/apache/ossie/converter/RelationshipMappingHandler.java @@ -1,23 +1,42 @@ -package org.osi.converter; +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.ossie.converter; -import static org.osi.converter.ConverterConstants.*; -import static org.osi.util.DataStructureUtils.*; +import static org.apache.ossie.converter.ConverterConstants.*; +import static org.apache.ossie.util.DataStructureUtils.*; -import org.osi.converter.ConverterConstants.Level; -import org.osi.converter.pipeline.PipelineStep; +import org.apache.ossie.converter.ConverterConstants.Level; +import org.apache.ossie.converter.pipeline.PipelineStep; import java.util.*; -import org.osi.util.MappingUtils; +import org.apache.ossie.util.MappingUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * Bidirectional handler for mapping relationships between OSI and Salesforce formats. + * Bidirectional handler for mapping relationships between Ossie and Salesforce formats. * *

Supports both conversion directions: *

    - *
  • OSI → Salesforce: from/to/from_columns/to_columns → criteria array
  • - *
  • Salesforce → OSI: criteria array → from/to/from_columns/to_columns
  • + *
  • Ossie → Salesforce: from/to/from_columns/to_columns → criteria array
  • + *
  • Salesforce → Ossie: criteria array → from/to/from_columns/to_columns
  • *
* */ @@ -44,7 +63,7 @@ public void execute(Map sourceData, Map outputDa } /** - * Maps OSI relationships to Salesforce semanticRelationships. + * Maps Ossie relationships to Salesforce semanticRelationships. */ private void mapOsiToSalesforce( Map sourceData, Map outputData, Map mappings) { @@ -84,7 +103,7 @@ private void mapOsiToSalesforce( } /** - * Maps Salesforce semanticRelationships to OSI relationships. + * Maps Salesforce semanticRelationships to Ossie relationships. */ private void mapSalesforceToOsi( Map sourceData, Map outputData, Map mappings) { @@ -147,7 +166,7 @@ private void mapSalesforceToOsi( } /** - * Reconstructs criteria for OSI→SF conversion. + * Reconstructs criteria for Ossie→SF conversion. */ private void reconstructCriteria(List osiRelationships, List sfRelationships) { for (int i = 0; i < osiRelationships.size() && i < sfRelationships.size(); i++) { @@ -200,7 +219,7 @@ private void reconstructCriteria(List osiRelationships, List sfR } /** - * Deconstructs criteria for SF→OSI conversion. + * Deconstructs criteria for SF→Ossie conversion. * Note: This method only processes supported relationships (TableField types). * Unsupported relationships are filtered out earlier and stored in custom_extensions. */ @@ -267,7 +286,7 @@ private void applyDefaults(List sfRelationships) { /** * Validates and filters relationships, removing those that reference non-existent fields. (Calculated fields that are not supported) * - * @param osiRelationships List of OSI relationships to validate + * @param osiRelationships List of Ossie relationships to validate * @param outputData The output data containing semanticDataObjects with their fields * @return Filtered list of valid relationships */ diff --git a/converters/salesforce/src/main/java/org/osi/converter/SemanticModelMappingHandler.java b/converters/salesforce/src/main/java/org/apache/ossie/converter/SemanticModelMappingHandler.java similarity index 68% rename from converters/salesforce/src/main/java/org/osi/converter/SemanticModelMappingHandler.java rename to converters/salesforce/src/main/java/org/apache/ossie/converter/SemanticModelMappingHandler.java index 5bbfbbd9..d6c997ce 100644 --- a/converters/salesforce/src/main/java/org/osi/converter/SemanticModelMappingHandler.java +++ b/converters/salesforce/src/main/java/org/apache/ossie/converter/SemanticModelMappingHandler.java @@ -1,27 +1,46 @@ -package org.osi.converter; +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.ossie.converter; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import org.osi.converter.ConverterConstants.Level; -import org.osi.converter.pipeline.PipelineStep; -import org.osi.exception.ConversionException; -import org.osi.util.MappingUtils; +import org.apache.ossie.converter.ConverterConstants.Level; +import org.apache.ossie.converter.pipeline.PipelineStep; +import org.apache.ossie.exception.ConversionException; +import org.apache.ossie.util.MappingUtils; import java.util.Map; import java.util.Set; -import static org.osi.converter.ConverterConstants.AI_CONTEXT; -import static org.osi.converter.ConverterConstants.API_NAME; -import static org.osi.converter.ConverterConstants.BUSINESS_PREFERENCES; -import static org.osi.converter.ConverterConstants.LABEL; +import static org.apache.ossie.converter.ConverterConstants.AI_CONTEXT; +import static org.apache.ossie.converter.ConverterConstants.API_NAME; +import static org.apache.ossie.converter.ConverterConstants.BUSINESS_PREFERENCES; +import static org.apache.ossie.converter.ConverterConstants.LABEL; /** * Bidirectional handler for mapping top-level semantic model properties. * *

Supports both conversion directions: *

    - *
  • OSI → Salesforce: name → apiName, apply SF defaults, restore custom_extensions
  • - *
  • Salesforce → OSI: apiName → name, strip SF defaults, store custom_extensions
  • + *
  • Ossie → Salesforce: name → apiName, apply SF defaults, restore custom_extensions
  • + *
  • Salesforce → Ossie: apiName → name, strip SF defaults, store custom_extensions
  • *
* *

Handles only top-level scalar properties (not arrays). @@ -49,7 +68,7 @@ public void execute(Map sourceData, Map outputDa } /** - * Maps OSI top-level properties to Salesforce format. + * Maps Ossie top-level properties to Salesforce format. * Steps: generic mappings → manual conversions → restore custom extensions */ private void mapOsiToSalesforce( @@ -66,7 +85,7 @@ private void mapOsiToSalesforce( } /** - * Maps Salesforce top-level properties to OSI format. + * Maps Salesforce top-level properties to Ossie format. * Steps: generic mappings → manual conversions → store custom extensions */ private void mapSalesforceToOsi( @@ -84,10 +103,10 @@ private void mapSalesforceToOsi( } /** - * Converts Salesforce businessPreferences to OSI ai_context. + * Converts Salesforce businessPreferences to Ossie ai_context. * * @param sourceData Salesforce data containing businessPreferences - * @param outputData OSI data to populate with ai_context + * @param outputData Ossie data to populate with ai_context */ private void convertBusinessPreferencesToAiContext(Map sourceData, Map outputData) { Object businessPreferences = sourceData.get(BUSINESS_PREFERENCES); @@ -97,7 +116,7 @@ private void convertBusinessPreferencesToAiContext(Map sourceDat } /** - * Converts OSI ai_context (string or object) to Salesforce businessPreferences (string). + * Converts Ossie ai_context (string or object) to Salesforce businessPreferences (string). * *

ai_context can be: *

    @@ -105,7 +124,7 @@ private void convertBusinessPreferencesToAiContext(Map sourceDat *
  • An object - serialized to JSON string
  • *
* - * @param sourceData OSI data containing ai_context + * @param sourceData Ossie data containing ai_context * @param outputData Salesforce data to populate with businessPreferences */ private void convertAiContextToBusinessPreferences(Map sourceData, Map outputData) { @@ -129,7 +148,7 @@ private void convertAiContextToBusinessPreferences(Map sourceDat /** * Applies default values for required Salesforce semantic model properties. - * Used when converting OSI → Salesforce. + * Used when converting Ossie → Salesforce. */ private void applyDefaults(Map outputData) { if (!outputData.containsKey(LABEL)) { diff --git a/converters/salesforce/src/main/java/org/osi/converter/pipeline/DirectionConfig.java b/converters/salesforce/src/main/java/org/apache/ossie/converter/pipeline/DirectionConfig.java similarity index 58% rename from converters/salesforce/src/main/java/org/osi/converter/pipeline/DirectionConfig.java rename to converters/salesforce/src/main/java/org/apache/ossie/converter/pipeline/DirectionConfig.java index b7df1dbe..0978f91f 100644 --- a/converters/salesforce/src/main/java/org/osi/converter/pipeline/DirectionConfig.java +++ b/converters/salesforce/src/main/java/org/apache/ossie/converter/pipeline/DirectionConfig.java @@ -1,6 +1,25 @@ -package org.osi.converter.pipeline; +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.ossie.converter.pipeline; -import static org.osi.converter.ConverterConstants.*; +import static org.apache.ossie.converter.ConverterConstants.*; /** * Configuration for a specific conversion direction. diff --git a/converters/salesforce/src/main/java/org/osi/converter/pipeline/HandlerFactory.java b/converters/salesforce/src/main/java/org/apache/ossie/converter/pipeline/HandlerFactory.java similarity index 62% rename from converters/salesforce/src/main/java/org/osi/converter/pipeline/HandlerFactory.java rename to converters/salesforce/src/main/java/org/apache/ossie/converter/pipeline/HandlerFactory.java index 4b98824a..f4f7591e 100644 --- a/converters/salesforce/src/main/java/org/osi/converter/pipeline/HandlerFactory.java +++ b/converters/salesforce/src/main/java/org/apache/ossie/converter/pipeline/HandlerFactory.java @@ -1,7 +1,26 @@ -package org.osi.converter.pipeline; +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.ossie.converter.pipeline; -import org.osi.converter.*; -import org.osi.exception.ConversionException; +import org.apache.ossie.converter.*; +import org.apache.ossie.exception.ConversionException; /** * Factory for creating handler instances using a hardcoded registry. diff --git a/converters/salesforce/src/main/java/org/apache/ossie/converter/pipeline/PipelineConfig.java b/converters/salesforce/src/main/java/org/apache/ossie/converter/pipeline/PipelineConfig.java new file mode 100644 index 00000000..d1c3cc72 --- /dev/null +++ b/converters/salesforce/src/main/java/org/apache/ossie/converter/pipeline/PipelineConfig.java @@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.ossie.converter.pipeline; + +import java.util.List; +import java.util.Map; + +/** + * Root configuration model matching pipeline-config.yaml structure. + * + */ +public class PipelineConfig { + private Map> pipelines; // Direction -> handler names + private Map directionConfigs; // Direction -> config + + public Map> getPipelines() { + return pipelines; + } + + public void setPipelines(Map> pipelines) { + this.pipelines = pipelines; + } + + public Map getDirectionConfigs() { + return directionConfigs; + } + + public void setDirectionConfigs(Map directionConfigs) { + this.directionConfigs = directionConfigs; + } +} diff --git a/converters/salesforce/src/main/java/org/osi/converter/pipeline/PipelineConfigLoader.java b/converters/salesforce/src/main/java/org/apache/ossie/converter/pipeline/PipelineConfigLoader.java similarity index 71% rename from converters/salesforce/src/main/java/org/osi/converter/pipeline/PipelineConfigLoader.java rename to converters/salesforce/src/main/java/org/apache/ossie/converter/pipeline/PipelineConfigLoader.java index 6805a543..6cf19133 100644 --- a/converters/salesforce/src/main/java/org/osi/converter/pipeline/PipelineConfigLoader.java +++ b/converters/salesforce/src/main/java/org/apache/ossie/converter/pipeline/PipelineConfigLoader.java @@ -1,9 +1,28 @@ -package org.osi.converter.pipeline; +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.ossie.converter.pipeline; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; -import org.osi.exception.ConversionException; +import org.apache.ossie.exception.ConversionException; import java.io.IOException; import java.io.InputStream; diff --git a/converters/salesforce/src/main/java/org/apache/ossie/converter/pipeline/PipelineStep.java b/converters/salesforce/src/main/java/org/apache/ossie/converter/pipeline/PipelineStep.java new file mode 100644 index 00000000..95fef55c --- /dev/null +++ b/converters/salesforce/src/main/java/org/apache/ossie/converter/pipeline/PipelineStep.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.ossie.converter.pipeline; + +import java.util.Map; + +/** + * Base interface for pipeline steps. + * Both mapping handlers and special wrapper steps implement this interface. + * + */ +public interface PipelineStep { + /** + * Execute this pipeline step. + * + * @param sourceData The source data (may be modified by handler) + * @param outputData The output data being built + * @param mappings Property mappings + */ + void execute(Map sourceData, Map outputData, Map mappings); +} diff --git a/converters/salesforce/src/main/java/org/apache/ossie/exception/ConversionException.java b/converters/salesforce/src/main/java/org/apache/ossie/exception/ConversionException.java new file mode 100644 index 00000000..704622d1 --- /dev/null +++ b/converters/salesforce/src/main/java/org/apache/ossie/exception/ConversionException.java @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.ossie.exception; + +/** + * Exception thrown when a conversion operation fails. + * This can happen during YAML to JSON or JSON to YAML conversion. + * + */ +public class ConversionException extends RuntimeException { + + /** + * Constructs a new ConversionException with the specified detail message. + * + * @param message the detail message + */ + public ConversionException(String message) { + super(message); + } + + /** + * Constructs a new ConversionException with the specified detail message and cause. + * + * @param message the detail message + * @param cause the cause of the exception + */ + public ConversionException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/converters/salesforce/src/main/java/org/apache/ossie/exception/InvalidInputException.java b/converters/salesforce/src/main/java/org/apache/ossie/exception/InvalidInputException.java new file mode 100644 index 00000000..0c52a8e7 --- /dev/null +++ b/converters/salesforce/src/main/java/org/apache/ossie/exception/InvalidInputException.java @@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.ossie.exception; + +/** + * Exception thrown when the input is invalid. + * This includes cases where the file does not exist, is not readable, + * or if the file/String contains invalid YAML/JSON content. + * + */ +public class InvalidInputException extends RuntimeException { + + /** + * Constructs a new InvalidInputException with the specified detail message. + * + * @param message the detail message + */ + public InvalidInputException(String message) { + super(message); + } + + /** + * Constructs a new InvalidInputException with the specified detail message and cause. + * + * @param message the detail message + * @param cause the cause of the exception + */ + public InvalidInputException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/converters/salesforce/src/main/java/org/apache/ossie/exception/ValidationException.java b/converters/salesforce/src/main/java/org/apache/ossie/exception/ValidationException.java new file mode 100644 index 00000000..f6c33b30 --- /dev/null +++ b/converters/salesforce/src/main/java/org/apache/ossie/exception/ValidationException.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.ossie.exception; + +/** + * Exception thrown when schema validation fails. + * + */ +public class ValidationException extends RuntimeException { + + public ValidationException(String message) { + super(message); + } + + public ValidationException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/converters/salesforce/src/main/java/org/osi/mapper/FileBasedPropertyMapper.java b/converters/salesforce/src/main/java/org/apache/ossie/mapper/FileBasedPropertyMapper.java similarity index 73% rename from converters/salesforce/src/main/java/org/osi/mapper/FileBasedPropertyMapper.java rename to converters/salesforce/src/main/java/org/apache/ossie/mapper/FileBasedPropertyMapper.java index 40d4783f..a40e6e02 100644 --- a/converters/salesforce/src/main/java/org/osi/mapper/FileBasedPropertyMapper.java +++ b/converters/salesforce/src/main/java/org/apache/ossie/mapper/FileBasedPropertyMapper.java @@ -1,9 +1,28 @@ -package org.osi.mapper; +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.ossie.mapper; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; -import org.osi.exception.InvalidInputException; +import org.apache.ossie.exception.InvalidInputException; import java.io.IOException; import java.io.InputStream; import java.util.Collections; @@ -14,7 +33,7 @@ /** * Property mapper that loads mappings from a YAML configuration file. * - *

This mapper reads the bundled mappings.yaml which defines OSI-Salesforce + *

This mapper reads the bundled mappings.yaml which defines Ossie-Salesforce * property mappings. The transformation is performed by the converter classes.

* */ @@ -28,7 +47,7 @@ public class FileBasedPropertyMapper implements PropertyMapper { /** * Constructs a FileBasedPropertyMapper with the specified mappings. * - * @param mappings the OSI-Salesforce mappings + * @param mappings the Ossie-Salesforce mappings */ public FileBasedPropertyMapper(Map mappings) { this.mappings = mappings != null ? new LinkedHashMap<>(mappings) : new LinkedHashMap<>(); @@ -36,9 +55,9 @@ public FileBasedPropertyMapper(Map mappings) { } /** - * Generates reverse mappings (Salesforce to OSI) from the OSI to Salesforce mappings. + * Generates reverse mappings (Salesforce to Ossie) from the Ossie to Salesforce mappings. * - * @return the Salesforce-to-OSI mappings + * @return the Salesforce-to-Ossie mappings */ private Map generateReverseMappings() { return mappings.entrySet().stream().collect(Collectors.toMap(Map.Entry::getValue, Map.Entry::getKey)); diff --git a/converters/salesforce/src/main/java/org/apache/ossie/mapper/PropertyMapper.java b/converters/salesforce/src/main/java/org/apache/ossie/mapper/PropertyMapper.java new file mode 100644 index 00000000..1c6ccf8c --- /dev/null +++ b/converters/salesforce/src/main/java/org/apache/ossie/mapper/PropertyMapper.java @@ -0,0 +1,49 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.ossie.mapper; + +import java.util.Map; + +/** + * Interface for mapping properties between Ossie and Salesforce formats during conversion. + * + *

Implementations of this interface define bidirectional mappings between Ossie YAML format + * and Salesforce JSON format. This supports nested paths using dot notation + * (e.g., "datasets.name" ↔ "semanticDataObjects.apiName").

+ * + * + */ +public interface PropertyMapper { + + /** + * Returns the mapping from Ossie property paths to Salesforce property paths. + * + * @return a map where keys are Ossie property paths and values are Salesforce property paths + */ + Map getOsiToSalesforceMappings(); + + /** + * Returns the mapping from Salesforce property paths to Ossie property paths. + *

This is the reverse mapping of {@link #getOsiToSalesforceMappings()}. + * + * @return a map where keys are Salesforce property paths and values are Ossie property paths + */ + Map getSalesforceToOsiMappings(); +} diff --git a/converters/salesforce/src/main/java/org/osi/util/DataStructureUtils.java b/converters/salesforce/src/main/java/org/apache/ossie/util/DataStructureUtils.java similarity index 81% rename from converters/salesforce/src/main/java/org/osi/util/DataStructureUtils.java rename to converters/salesforce/src/main/java/org/apache/ossie/util/DataStructureUtils.java index 87f3a02f..4b409287 100644 --- a/converters/salesforce/src/main/java/org/osi/util/DataStructureUtils.java +++ b/converters/salesforce/src/main/java/org/apache/ossie/util/DataStructureUtils.java @@ -1,4 +1,23 @@ -package org.osi.util; +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.ossie.util; import java.util.ArrayList; import java.util.List; diff --git a/converters/salesforce/src/main/java/org/osi/util/MappingUtils.java b/converters/salesforce/src/main/java/org/apache/ossie/util/MappingUtils.java similarity index 81% rename from converters/salesforce/src/main/java/org/osi/util/MappingUtils.java rename to converters/salesforce/src/main/java/org/apache/ossie/util/MappingUtils.java index cb17380c..014a9e8c 100644 --- a/converters/salesforce/src/main/java/org/osi/util/MappingUtils.java +++ b/converters/salesforce/src/main/java/org/apache/ossie/util/MappingUtils.java @@ -1,4 +1,23 @@ -package org.osi.util; +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.ossie.util; import java.util.*; diff --git a/converters/salesforce/src/main/java/org/osi/util/PathUtils.java b/converters/salesforce/src/main/java/org/apache/ossie/util/PathUtils.java similarity index 77% rename from converters/salesforce/src/main/java/org/osi/util/PathUtils.java rename to converters/salesforce/src/main/java/org/apache/ossie/util/PathUtils.java index 4ed48611..461dd109 100644 --- a/converters/salesforce/src/main/java/org/osi/util/PathUtils.java +++ b/converters/salesforce/src/main/java/org/apache/ossie/util/PathUtils.java @@ -1,7 +1,26 @@ -package org.osi.util; +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.ossie.util; -import static org.osi.util.DataStructureUtils.asMap; -import static org.osi.util.DataStructureUtils.asList; +import static org.apache.ossie.util.DataStructureUtils.asMap; +import static org.apache.ossie.util.DataStructureUtils.asList; import java.util.ArrayList; import java.util.LinkedHashMap; diff --git a/converters/salesforce/src/main/java/org/osi/validator/SchemaValidator.java b/converters/salesforce/src/main/java/org/apache/ossie/validator/SchemaValidator.java similarity index 77% rename from converters/salesforce/src/main/java/org/osi/validator/SchemaValidator.java rename to converters/salesforce/src/main/java/org/apache/ossie/validator/SchemaValidator.java index 9cd0adaf..b1bea2c0 100644 --- a/converters/salesforce/src/main/java/org/osi/validator/SchemaValidator.java +++ b/converters/salesforce/src/main/java/org/apache/ossie/validator/SchemaValidator.java @@ -1,4 +1,23 @@ -package org.osi.validator; +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.ossie.validator; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; @@ -11,12 +30,12 @@ import java.util.Set; import java.util.stream.Collectors; -import org.osi.exception.ValidationException; +import org.apache.ossie.exception.ValidationException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * Validates OSI semantic model data against JSON Schema. + * Validates Ossie semantic model data against JSON Schema. * *

Uses networknt/json-schema-validator for schema validation. * Collects all validation errors for better developer experience. @@ -65,7 +84,7 @@ private JsonSchema loadSchema() { } /** - * Validates OSI semantic model data against the schema. + * Validates Ossie semantic model data against the schema. * * @param data The semantic model data to validate * @throws ValidationException if validation fails with details of all errors diff --git a/converters/salesforce/src/main/java/org/osi/converter/ConversionDirection.java b/converters/salesforce/src/main/java/org/osi/converter/ConversionDirection.java deleted file mode 100644 index e87a1b5a..00000000 --- a/converters/salesforce/src/main/java/org/osi/converter/ConversionDirection.java +++ /dev/null @@ -1,30 +0,0 @@ -package org.osi.converter; - -/** - * Enum representing the direction of conversion. - * - */ -public enum ConversionDirection { - /** - * Converting from OSI YAML format to Salesforce JSON format. - */ - OSI_TO_SALESFORCE, - - /** - * Converting from Salesforce JSON format to OSI YAML format. - */ - SALESFORCE_TO_OSI; - - /** - * Converts enum name to pipeline configuration key. - * Maps OSI_TO_SALESFORCE -> "osiToSalesforce" and SALESFORCE_TO_OSI -> "salesforceToOsi" - * - * @return The pipeline key used in YAML configuration - */ - public String toPipelineKey() { - return switch (this) { - case OSI_TO_SALESFORCE -> "osiToSalesforce"; - case SALESFORCE_TO_OSI -> "salesforceToOsi"; - }; - } -} diff --git a/converters/salesforce/src/main/java/org/osi/converter/Converter.java b/converters/salesforce/src/main/java/org/osi/converter/Converter.java deleted file mode 100644 index 97d08481..00000000 --- a/converters/salesforce/src/main/java/org/osi/converter/Converter.java +++ /dev/null @@ -1,36 +0,0 @@ -package org.osi.converter; - -import java.nio.file.Path; -import java.util.List; - -/** - * Interface for converting between data formats (YAML and JSON). - * - *

This is the external API for conversion. Implementations handle the conversion - * of data from one format to another, with support for property mapping.

- * - */ -public interface Converter { - - /** - * Converts the input file and writes results to the specified output directory. - * - *

Each semantic model is written to a separate file named after its apiName. - * Example: "Sales_Model.json", "Marketing_Model.json" - * - * @param inputPath the path to the input file - * @param outputDir the directory where output files will be written - */ - void convert(Path inputPath, Path outputDir); - - /** - * Converts string content from the source format to the target format. - * - *

For OSI to Salesforce: returns one Salesforce model per OSI semantic_model entry. - *

For Salesforce to OSI: returns one OSI document with one semantic_model entry. - * - * @param content the content to convert - * @return list of converted content strings (one per semantic model) - */ - List convert(String content); -} diff --git a/converters/salesforce/src/main/java/org/osi/converter/ConverterFactory.java b/converters/salesforce/src/main/java/org/osi/converter/ConverterFactory.java deleted file mode 100644 index 9e78e9b0..00000000 --- a/converters/salesforce/src/main/java/org/osi/converter/ConverterFactory.java +++ /dev/null @@ -1,18 +0,0 @@ -package org.osi.converter; - -/** - * Factory for creating converters based on conversion direction. - * - */ -public class ConverterFactory { - - /** - * Creates a converter for the specified direction. - * - * @param direction The conversion direction - * @return A Converter instance configured for the specified direction - */ - public static Converter getConverter(ConversionDirection direction) { - return new ConverterImpl(direction); - } -} diff --git a/converters/salesforce/src/main/java/org/osi/converter/pipeline/PipelineConfig.java b/converters/salesforce/src/main/java/org/osi/converter/pipeline/PipelineConfig.java deleted file mode 100644 index ec9f1423..00000000 --- a/converters/salesforce/src/main/java/org/osi/converter/pipeline/PipelineConfig.java +++ /dev/null @@ -1,29 +0,0 @@ -package org.osi.converter.pipeline; - -import java.util.List; -import java.util.Map; - -/** - * Root configuration model matching pipeline-config.yaml structure. - * - */ -public class PipelineConfig { - private Map> pipelines; // Direction -> handler names - private Map directionConfigs; // Direction -> config - - public Map> getPipelines() { - return pipelines; - } - - public void setPipelines(Map> pipelines) { - this.pipelines = pipelines; - } - - public Map getDirectionConfigs() { - return directionConfigs; - } - - public void setDirectionConfigs(Map directionConfigs) { - this.directionConfigs = directionConfigs; - } -} diff --git a/converters/salesforce/src/main/java/org/osi/converter/pipeline/PipelineStep.java b/converters/salesforce/src/main/java/org/osi/converter/pipeline/PipelineStep.java deleted file mode 100644 index bec154d2..00000000 --- a/converters/salesforce/src/main/java/org/osi/converter/pipeline/PipelineStep.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.osi.converter.pipeline; - -import java.util.Map; - -/** - * Base interface for pipeline steps. - * Both mapping handlers and special wrapper steps implement this interface. - * - */ -public interface PipelineStep { - /** - * Execute this pipeline step. - * - * @param sourceData The source data (may be modified by handler) - * @param outputData The output data being built - * @param mappings Property mappings - */ - void execute(Map sourceData, Map outputData, Map mappings); -} diff --git a/converters/salesforce/src/main/java/org/osi/exception/ConversionException.java b/converters/salesforce/src/main/java/org/osi/exception/ConversionException.java deleted file mode 100644 index f9d8fdda..00000000 --- a/converters/salesforce/src/main/java/org/osi/exception/ConversionException.java +++ /dev/null @@ -1,28 +0,0 @@ -package org.osi.exception; - -/** - * Exception thrown when a conversion operation fails. - * This can happen during YAML to JSON or JSON to YAML conversion. - * - */ -public class ConversionException extends RuntimeException { - - /** - * Constructs a new ConversionException with the specified detail message. - * - * @param message the detail message - */ - public ConversionException(String message) { - super(message); - } - - /** - * Constructs a new ConversionException with the specified detail message and cause. - * - * @param message the detail message - * @param cause the cause of the exception - */ - public ConversionException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/converters/salesforce/src/main/java/org/osi/exception/InvalidInputException.java b/converters/salesforce/src/main/java/org/osi/exception/InvalidInputException.java deleted file mode 100644 index 079dc63f..00000000 --- a/converters/salesforce/src/main/java/org/osi/exception/InvalidInputException.java +++ /dev/null @@ -1,29 +0,0 @@ -package org.osi.exception; - -/** - * Exception thrown when the input is invalid. - * This includes cases where the file does not exist, is not readable, - * or if the file/String contains invalid YAML/JSON content. - * - */ -public class InvalidInputException extends RuntimeException { - - /** - * Constructs a new InvalidInputException with the specified detail message. - * - * @param message the detail message - */ - public InvalidInputException(String message) { - super(message); - } - - /** - * Constructs a new InvalidInputException with the specified detail message and cause. - * - * @param message the detail message - * @param cause the cause of the exception - */ - public InvalidInputException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/converters/salesforce/src/main/java/org/osi/exception/ValidationException.java b/converters/salesforce/src/main/java/org/osi/exception/ValidationException.java deleted file mode 100644 index 68d6268b..00000000 --- a/converters/salesforce/src/main/java/org/osi/exception/ValidationException.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.osi.exception; - -/** - * Exception thrown when schema validation fails. - * - */ -public class ValidationException extends RuntimeException { - - public ValidationException(String message) { - super(message); - } - - public ValidationException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/converters/salesforce/src/main/java/org/osi/mapper/PropertyMapper.java b/converters/salesforce/src/main/java/org/osi/mapper/PropertyMapper.java deleted file mode 100644 index af8789bc..00000000 --- a/converters/salesforce/src/main/java/org/osi/mapper/PropertyMapper.java +++ /dev/null @@ -1,30 +0,0 @@ -package org.osi.mapper; - -import java.util.Map; - -/** - * Interface for mapping properties between OSI and Salesforce formats during conversion. - * - *

Implementations of this interface define bidirectional mappings between OSI YAML format - * and Salesforce JSON format. This supports nested paths using dot notation - * (e.g., "datasets.name" ↔ "semanticDataObjects.apiName").

- * - * - */ -public interface PropertyMapper { - - /** - * Returns the mapping from OSI property paths to Salesforce property paths. - * - * @return a map where keys are OSI property paths and values are Salesforce property paths - */ - Map getOsiToSalesforceMappings(); - - /** - * Returns the mapping from Salesforce property paths to OSI property paths. - *

This is the reverse mapping of {@link #getOsiToSalesforceMappings()}. - * - * @return a map where keys are Salesforce property paths and values are OSI property paths - */ - Map getSalesforceToOsiMappings(); -} diff --git a/converters/salesforce/src/main/resources/mappings.yaml b/converters/salesforce/src/main/resources/mappings.yaml index fddc085f..57552cf8 100644 --- a/converters/salesforce/src/main/resources/mappings.yaml +++ b/converters/salesforce/src/main/resources/mappings.yaml @@ -1,8 +1,25 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + # ============================================================================= -# OSI-Salesforce Converter - Mapping Configuration +# Ossie-Salesforce Converter - Mapping Configuration # ============================================================================= # -# This file defines ONLY straightforward property mappings between OSI YAML +# This file defines ONLY straightforward property mappings between Ossie YAML # format and Salesforce Semantic Model JSON format. # # Complex mappings (arrays, routing, transformations) are handled programmatically diff --git a/converters/salesforce/src/main/resources/osi-salesforce-converter-config.yaml b/converters/salesforce/src/main/resources/osi-salesforce-converter-config.yaml index fea5fea0..371c90d8 100644 --- a/converters/salesforce/src/main/resources/osi-salesforce-converter-config.yaml +++ b/converters/salesforce/src/main/resources/osi-salesforce-converter-config.yaml @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + # Pipeline definitions - each direction lists handlers to execute in order pipelines: diff --git a/converters/salesforce/src/test/java/org/osi/OsiToSalesforceConverterTest.java b/converters/salesforce/src/test/java/org/apache/ossie/OsiToSalesforceConverterTest.java similarity index 89% rename from converters/salesforce/src/test/java/org/osi/OsiToSalesforceConverterTest.java rename to converters/salesforce/src/test/java/org/apache/ossie/OsiToSalesforceConverterTest.java index defdd367..94fa6b2b 100644 --- a/converters/salesforce/src/test/java/org/osi/OsiToSalesforceConverterTest.java +++ b/converters/salesforce/src/test/java/org/apache/ossie/OsiToSalesforceConverterTest.java @@ -1,19 +1,38 @@ -package org.osi; +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.ossie; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; -import org.osi.converter.Converter; -import org.osi.converter.ConverterFactory; -import org.osi.converter.ConversionDirection; -import org.osi.converter.CustomExtensionHandler; -import org.osi.validator.SchemaValidator; +import org.apache.ossie.converter.Converter; +import org.apache.ossie.converter.ConverterFactory; +import org.apache.ossie.converter.ConversionDirection; +import org.apache.ossie.converter.CustomExtensionHandler; +import org.apache.ossie.validator.SchemaValidator; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.osi.converter.pipeline.DirectionConfig; -import org.osi.converter.pipeline.HandlerFactory; -import org.osi.converter.pipeline.PipelineConfig; -import org.osi.converter.pipeline.PipelineConfigLoader; +import org.apache.ossie.converter.pipeline.DirectionConfig; +import org.apache.ossie.converter.pipeline.HandlerFactory; +import org.apache.ossie.converter.pipeline.PipelineConfig; +import org.apache.ossie.converter.pipeline.PipelineConfigLoader; import java.io.IOException; import java.nio.file.Files; @@ -25,7 +44,7 @@ import static org.junit.jupiter.api.Assumptions.assumeTrue; /** - * Comprehensive integration test for OSI → Salesforce conversion. + * Comprehensive integration test for Ossie → Salesforce conversion. * Uses real example file: osiToSalesforce.yaml */ class OsiToSalesforceConverterTest { @@ -55,10 +74,10 @@ static void checkSchemaAvailability() { System.err.println(" and save it to: src/main/resources/schemas/salesforce-semantic-model-schema.json\n"); } if (!osiSchemaExists) { - System.err.println("\n WARNING: OSI schema not found at " + SchemaValidator.OSI_SCHEMA_PATH); + System.err.println("\n WARNING: Ossie schema not found at " + SchemaValidator.OSI_SCHEMA_PATH); System.err.println(" Skipping OsiToSalesforceConverterTest tests."); System.err.println(" To run these tests, download the schema from:"); - System.err.println(" https://github.com/open-semantic-interchange/OSI/blob/main/core-spec/osi-schema.json"); + System.err.println(" https://github.com/apache/ossie/blob/main/core-spec/osi-schema.json"); System.err.println(" and save it to: src/main/resources/schemas/osi-schema.json\n"); } warningPrinted = true; @@ -67,7 +86,7 @@ static void checkSchemaAvailability() { @BeforeEach void setUp() throws IOException { - assumeTrue(osiSchemaExists, "OSI schema file is required but not found. See README for setup instructions."); + assumeTrue(osiSchemaExists, "Ossie schema file is required but not found. See README for setup instructions."); converter = ConverterFactory.getConverter(ConversionDirection.OSI_TO_SALESFORCE); jsonMapper = new ObjectMapper(); @@ -255,7 +274,7 @@ void testMetricsNotConvertedInOsiToSalesforce() throws Exception { Map sfModel = jsonMapper.readValue(results.get(0), new TypeReference>() {}); List> calcMeasurements = (List>) sfModel.get("semanticCalculatedMeasurements"); - assertNull(calcMeasurements, "Metrics from OSI are not converted to semanticCalculatedMeasurements in OSI->SF direction"); + assertNull(calcMeasurements, "Metrics from Ossie are not converted to semanticCalculatedMeasurements in Ossie->SF direction"); } @Test diff --git a/converters/salesforce/src/test/java/org/osi/SalesforceToOsiConverterTest.java b/converters/salesforce/src/test/java/org/apache/ossie/SalesforceToOsiConverterTest.java similarity index 90% rename from converters/salesforce/src/test/java/org/osi/SalesforceToOsiConverterTest.java rename to converters/salesforce/src/test/java/org/apache/ossie/SalesforceToOsiConverterTest.java index 9a11af79..19268434 100644 --- a/converters/salesforce/src/test/java/org/osi/SalesforceToOsiConverterTest.java +++ b/converters/salesforce/src/test/java/org/apache/ossie/SalesforceToOsiConverterTest.java @@ -1,16 +1,35 @@ -package org.osi; +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.ossie; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; -import org.osi.converter.Converter; -import org.osi.converter.ConverterFactory; -import org.osi.converter.ConversionDirection; -import org.osi.converter.CustomExtensionHandler; -import org.osi.validator.SchemaValidator; +import org.apache.ossie.converter.Converter; +import org.apache.ossie.converter.ConverterFactory; +import org.apache.ossie.converter.ConversionDirection; +import org.apache.ossie.converter.CustomExtensionHandler; +import org.apache.ossie.validator.SchemaValidator; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.osi.converter.pipeline.*; +import org.apache.ossie.converter.pipeline.*; import java.io.IOException; import java.nio.file.Files; @@ -22,7 +41,7 @@ import static org.junit.jupiter.api.Assumptions.assumeTrue; /** - * Comprehensive integration test for Salesforce → OSI conversion. + * Comprehensive integration test for Salesforce → Ossie conversion. * Uses real example file: salesforceToOsi.json */ class SalesforceToOsiConverterTest { @@ -51,10 +70,10 @@ static void checkSchemaAvailability() { System.err.println(" and save it to: src/main/resources/schemas/salesforce-semantic-model-schema.json\n"); } if (!osiSchemaExists) { - System.err.println("\n WARNING: OSI schema not found at " + SchemaValidator.OSI_SCHEMA_PATH); + System.err.println("\n WARNING: Ossie schema not found at " + SchemaValidator.OSI_SCHEMA_PATH); System.err.println(" Skipping SalesforceToOsiConverterTest tests."); System.err.println(" To run these tests, download the schema from:"); - System.err.println(" https://github.com/open-semantic-interchange/OSI/blob/main/core-spec/osi-schema.json"); + System.err.println(" https://github.com/apache/ossie/blob/main/core-spec/osi-schema.json"); System.err.println(" and save it to: src/main/resources/schemas/osi-schema.json\n"); } warningPrinted = true; @@ -64,7 +83,7 @@ static void checkSchemaAvailability() { @BeforeEach void setUp() throws IOException { assumeTrue(schemaExists, "Salesforce schema file is required but not found. See README for setup instructions."); - assumeTrue(osiSchemaExists, "OSI schema file is required but not found. See README for setup instructions."); + assumeTrue(osiSchemaExists, "Ossie schema file is required but not found. See README for setup instructions."); converter = ConverterFactory.getConverter(ConversionDirection.SALESFORCE_TO_OSI); yamlMapper = new ObjectMapper(new YAMLFactory()); @@ -303,7 +322,7 @@ void testOutputCompilesWithOsiSchema() throws Exception { Map osiRoot = yamlMapper.readValue(osiYaml, Map.class); SchemaValidator validator = new SchemaValidator(new ObjectMapper(), SchemaValidator.OSI_SCHEMA_PATH); - assertDoesNotThrow(() -> validator.validate(osiRoot), "Output should comply with OSI schema"); + assertDoesNotThrow(() -> validator.validate(osiRoot), "Output should comply with Ossie schema"); } @Test @@ -368,11 +387,11 @@ void testHandlerFactoryForSalesforceToOsi() { @Test void testConverterImplExtractModelNameFromOsiFormat() throws Exception { - // Test extractModelName specifically handles OSI wrapped format + // Test extractModelName specifically handles Ossie wrapped format List results = converter.convert(salesforceJson); String osiYaml = results.get(0); - // The result is wrapped OSI format - extractModelName should handle this + // The result is wrapped Ossie format - extractModelName should handle this Map osiRoot = yamlMapper.readValue(osiYaml, Map.class); assertTrue(osiRoot.containsKey("semantic_model")); diff --git a/converters/salesforce/src/test/resources/examples/osiToSalesforce.yaml b/converters/salesforce/src/test/resources/examples/osiToSalesforce.yaml index 46e578be..edb1859b 100644 --- a/converters/salesforce/src/test/resources/examples/osiToSalesforce.yaml +++ b/converters/salesforce/src/test/resources/examples/osiToSalesforce.yaml @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + version: 0.2.0.dev0 semantic_model: - name: Customer_Orders_Model diff --git a/converters/snowflake/README.md b/converters/snowflake/README.md index 93ee9616..249624ed 100644 --- a/converters/snowflake/README.md +++ b/converters/snowflake/README.md @@ -1,6 +1,25 @@ -# OSI to Snowflake Converter + + +# Apache Ossie to Snowflake Converter + +Converts Ossie YAML semantic models to [Snowflake Cortex Analyst](https://docs.snowflake.com/en/user-guide/snowflake-cortex/cortex-analyst) semantic model YAML. Pure offline conversion — no Snowflake connection required. > **Note:** This converter is under active development. It handles common cases but has not been thoroughly tested against all edge cases — use with caution in production. @@ -24,4 +43,4 @@ python3 -m pytest tests/ ## Limitations -Some OSI concepts (e.g., `ai_context` on relationships) do not have a native counterpart in the Snowflake semantic model. These are dropped during conversion and the converter will emit warnings so you know what was left behind. +Some Ossie concepts (e.g., `ai_context` on relationships) do not have a native counterpart in the Snowflake semantic model. These are dropped during conversion and the converter will emit warnings so you know what was left behind. diff --git a/converters/snowflake/requirements.txt b/converters/snowflake/requirements.txt index 8d5fd986..c5dcb696 100644 --- a/converters/snowflake/requirements.txt +++ b/converters/snowflake/requirements.txt @@ -1 +1,18 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + PyYAML>=5.0 diff --git a/converters/snowflake/src/osi_to_snowflake_yaml_converter.py b/converters/snowflake/src/osi_to_snowflake_yaml_converter.py index f2a8520b..17ef3876 100644 --- a/converters/snowflake/src/osi_to_snowflake_yaml_converter.py +++ b/converters/snowflake/src/osi_to_snowflake_yaml_converter.py @@ -1,5 +1,22 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + """ -Converts an OSI (Open Semantic Interchange) YAML semantic model to a Snowflake +Converts an Ossie (Open Semantic Interchange) YAML semantic model to a Snowflake Cortex Analyst semantic model YAML. Pure offline conversion — no Snowflake connection required. @@ -18,21 +35,21 @@ class OsiConversionError(Exception): - """Raised when an OSI YAML cannot be converted to Snowflake format.""" + """Raised when an Ossie YAML cannot be converted to Snowflake format.""" def convert_osi_to_snowflake(osi_yaml_str): - """Top-level entry point. Parses OSI YAML, validates, converts, returns + """Top-level entry point. Parses Ossie YAML, validates, converts, returns Snowflake YAML string. - Expects the standard OSI wrapped format:: + Expects the standard Ossie wrapped format:: version: "0.2.0.dev0" semantic_model: - name: ... Args: - osi_yaml_str: OSI YAML as a string. + osi_yaml_str: Ossie YAML as a string. Returns: Snowflake Cortex Analyst semantic model YAML string. @@ -42,34 +59,34 @@ def convert_osi_to_snowflake(osi_yaml_str): """ root = yaml.safe_load(osi_yaml_str) if not isinstance(root, dict): - raise OsiConversionError("Invalid OSI YAML: expected a mapping at the root") + raise OsiConversionError("Invalid Ossie YAML: expected a mapping at the root") version_str = str(root.get("version", "")) if version_str != SUPPORTED_VERSION: raise OsiConversionError( - f"Unsupported OSI specification version '{version_str}'. " + f"Unsupported Ossie specification version '{version_str}'. " f"Supported: {SUPPORTED_VERSION}" ) semantic_model = root.get("semantic_model") if not isinstance(semantic_model, list) or len(semantic_model) == 0: raise OsiConversionError( - "Invalid OSI YAML: 'semantic_model' must be a non-empty list" + "Invalid Ossie YAML: 'semantic_model' must be a non-empty list" ) if len(semantic_model) > 1: warnings.warn( - f"OSI YAML contains {len(semantic_model)} semantic models; " + f"Ossie YAML contains {len(semantic_model)} semantic models; " f"only the first will be converted" ) - osi = semantic_model[0] - if not isinstance(osi, dict): + ossie = semantic_model[0] + if not isinstance(ossie, dict): raise OsiConversionError( - "Invalid OSI YAML: 'semantic_model' entries must be mappings" + "Invalid Ossie YAML: 'semantic_model' entries must be mappings" ) - snowflake_model = _convert_model(osi) + snowflake_model = _convert_model(ossie) return yaml.dump( snowflake_model, @@ -79,38 +96,38 @@ def convert_osi_to_snowflake(osi_yaml_str): ) -def _convert_model(osi): - """Converts the root OSI model dict to a Snowflake semantic model dict.""" - name = osi.get("name") +def _convert_model(ossie): + """Converts the root Ossie model dict to a Snowflake semantic model dict.""" + name = ossie.get("name") if not name: raise OsiConversionError("Missing required 'name' field in semantic model") result = {} result["name"] = name - description = osi.get("description") - ai_context = osi.get("ai_context") + description = ossie.get("description") + ai_context = ossie.get("ai_context") if isinstance(ai_context, str) and ai_context: description = f"{description}\n{ai_context}" if description else ai_context if description: result["description"] = description # datasets -> tables - datasets = osi.get("datasets") + datasets = ossie.get("datasets") if datasets: tables = [_convert_dataset(ds) for ds in datasets] if tables: result["tables"] = tables # relationships - relationships = osi.get("relationships") + relationships = ossie.get("relationships") if relationships: converted_rels = [_convert_relationship(rel) for rel in relationships] if converted_rels: result["relationships"] = converted_rels # metrics - metrics = osi.get("metrics") + metrics = ossie.get("metrics") if metrics: converted_metrics = [] for m in metrics: @@ -121,13 +138,13 @@ def _convert_model(osi): result["metrics"] = converted_metrics dropped_ai = ["ai_context"] if isinstance(ai_context, dict) and ai_context else [] - _warn_dropped_fields(osi, "model", extra_dropped=dropped_ai) + _warn_dropped_fields(ossie, "model", extra_dropped=dropped_ai) return result def _convert_dataset(dataset): - """Converts an OSI dataset dict to a Snowflake table dict.""" + """Converts an Ossie dataset dict to a Snowflake table dict.""" result = {} name = dataset.get("name") if not name: @@ -209,11 +226,11 @@ def _classify_field(field): def _convert_named_expr(entry, kind): - """Converts an OSI field or metric dict to a Snowflake entry with name, expr, + """Converts an Ossie field or metric dict to a Snowflake entry with name, expr, description, and synonyms. Args: - entry: The OSI field or metric dict. + entry: The Ossie field or metric dict. kind: Human-readable type for error messages (e.g., "field", "metric"). """ name = entry.get("name") @@ -250,7 +267,7 @@ def _convert_named_expr(entry, kind): def _convert_relationship(rel): - """Converts an OSI relationship dict to a Snowflake relationship dict.""" + """Converts an Ossie relationship dict to a Snowflake relationship dict.""" result = {} rel_name = rel.get("name") if not rel_name: @@ -342,7 +359,7 @@ def _normalize_identifier(identifier): def _parse_source(source): - """Parses an OSI dataset source string into a Snowflake base_table dict. + """Parses an Ossie dataset source string into a Snowflake base_table dict. Returns None if source is empty/None. Returns {"definition": source} for subqueries. Otherwise splits into 3-part db.schema.table. @@ -393,13 +410,13 @@ def _extract_synonyms(ai_context): def _warn_dropped_fields(source, context, extra_dropped=None): - """Warns about OSI fields that have no Snowflake counterpart and are dropped. + """Warns about Ossie fields that have no Snowflake counterpart and are dropped. Checks for universally-dropped fields (custom_extensions, label, version). Callers handle ai_context warnings themselves since consumption logic varies. Args: - source: The OSI dict being converted. + source: The Ossie dict being converted. context: Human-readable description (e.g., "field 'col1'"). extra_dropped: Optional list of additional field descriptions to report as dropped (e.g., ai_context details computed by the caller). @@ -424,10 +441,10 @@ def _warn_dropped_fields(source, context, extra_dropped=None): def main(): parser = argparse.ArgumentParser( - description="Convert OSI YAML semantic model to Snowflake Cortex Analyst YAML" + description="Convert Ossie YAML semantic model to Snowflake Cortex Analyst YAML" ) parser.add_argument( - "-i", "--input", required=True, help="Path to the OSI YAML input file" + "-i", "--input", required=True, help="Path to the Ossie YAML input file" ) parser.add_argument( "-o", "--output", required=True, help="Path to write the Snowflake YAML output" diff --git a/converters/snowflake/tests/example_converted_tpcds_semantic_model.yaml b/converters/snowflake/tests/example_converted_tpcds_semantic_model.yaml index 9220ab64..b2d4aff7 100644 --- a/converters/snowflake/tests/example_converted_tpcds_semantic_model.yaml +++ b/converters/snowflake/tests/example_converted_tpcds_semantic_model.yaml @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + name: tpcds_retail_model description: TPC-DS retail semantic model for sales and customer analytics tables: diff --git a/converters/snowflake/tests/test_osi_to_snowflake_yaml_converter.py b/converters/snowflake/tests/test_osi_to_snowflake_yaml_converter.py index 7a8de1c0..0c732e85 100644 --- a/converters/snowflake/tests/test_osi_to_snowflake_yaml_converter.py +++ b/converters/snowflake/tests/test_osi_to_snowflake_yaml_converter.py @@ -1,4 +1,21 @@ -"""Tests for the OSI to Snowflake YAML converter.""" +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +"""Tests for the Ossie to Snowflake YAML converter.""" import sys import warnings @@ -29,7 +46,7 @@ # --------------------------------------------------------------------------- def _wrap_osi(model_dict): - """Wrap a model dict in the standard OSI envelope.""" + """Wrap a model dict in the standard Ossie envelope.""" return yaml.dump( {"version": "0.2.0.dev0", "semantic_model": [model_dict]}, default_flow_style=False, @@ -37,7 +54,7 @@ def _wrap_osi(model_dict): def _minimal_model(**overrides): - """Return a minimal valid OSI model dict.""" + """Return a minimal valid Ossie model dict.""" base = { "name": "test_model", "datasets": [ @@ -478,7 +495,7 @@ def test_invalid_yaml_root_raises(self): def test_wrong_version_raises(self): bad = yaml.dump({"version": "9.9.9", "semantic_model": [{"name": "m"}]}) - with pytest.raises(OsiConversionError, match="Unsupported OSI specification"): + with pytest.raises(OsiConversionError, match="Unsupported Ossie specification"): convert_osi_to_snowflake(bad) def test_missing_semantic_model_raises(self): @@ -605,7 +622,7 @@ def test_subquery_source(self): # --------------------------------------------------------------------------- -# _warn_dropped_fields (OSI concepts with no Snowflake counterpart) +# _warn_dropped_fields (Ossie concepts with no Snowflake counterpart) # --------------------------------------------------------------------------- class TestWarnDroppedFields: diff --git a/core-spec/osi-schema.json b/core-spec/osi-schema.json index 72cb164d..1732d4d5 100644 --- a/core-spec/osi-schema.json +++ b/core-spec/osi-schema.json @@ -1,14 +1,14 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://github.com/open-semantic-interchange/OSI/core-spec/osi-schema.json", - "title": "OSI Core Metadata Specification", - "description": "JSON Schema for validating OSI (Open Semantic Interoperability) semantic model definitions", + "$id": "https://github.com/apache/ossie/core-spec/osi-schema.json", + "title": "Apache Ossie Core Metadata Specification", + "description": "JSON Schema for validating Apache Ossie semantic model definitions", "type": "object", "properties": { "version": { "type": "string", "const": "0.2.0.dev0", - "description": "OSI specification version" + "description": "Apache Ossie specification version" }, "semantic_model": { "type": "array", diff --git a/core-spec/spec.md b/core-spec/spec.md index ea7e775d..b5440426 100644 --- a/core-spec/spec.md +++ b/core-spec/spec.md @@ -1,4 +1,23 @@ -# OSI - Core Metadata Specification + + +# Apache Ossie - Core Metadata Specification > **DRAFT version** — in development, schema may change before 0.2.0 is released. diff --git a/core-spec/spec.yaml b/core-spec/spec.yaml index ed462032..316d7840 100644 --- a/core-spec/spec.yaml +++ b/core-spec/spec.yaml @@ -1,4 +1,21 @@ -# OSI - Core Metadata Spec (YAML Schema) +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +# Apache Ossie - Core Metadata Spec (YAML Schema) # DRAFT version - in development, schema may change before 0.2.0 is released version: 0.2.0.dev0 # diff --git a/docs/index.md b/docs/index.md index 435631c0..661d76dd 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,10 +1,31 @@ -# Open Semantic Interchange (OSI) + + +# Apache Ossie ## Overview -The [Open Semantic Interchange (OSI)](https://open-semantic-interchange.org/) initiative is a collaborative, open-source effort dedicated to standardizing and streamlining semantic model exchange and utilization across the data analytics, AI, and BI ecosystem. Our shared vision is to establish a common, vendor-agnostic semantic model specification, promoting interoperability, efficiency, and collaboration among all participants. +The [Apache Ossie](https://ossie.apache.org/) initiative is a collaborative, open-source effort dedicated to standardizing and streamlining semantic model exchange and utilization across the data analytics, AI, and BI ecosystem. Our shared vision is to establish a common, vendor-agnostic semantic model specification, promoting interoperability, efficiency, and collaboration among all participants. -By providing a single, consistent source of truth, the OSI standard ensures that your data's definitions and value remain consistent as they are interchanged between AI agents, BI platforms, and all other tools in your ecosystem — eliminating inconsistencies across your different tools. +Apache Ossie was formerly known as **Open Semantic Interchange (OSI)**. + +By providing a single, consistent source of truth, the Ossie standard ensures that your data's definitions and value remain consistent as they are interchanged between AI agents, BI platforms, and all other tools in your ecosystem — eliminating inconsistencies across your different tools. ### The Problem: Semantic Fragmentation @@ -15,18 +36,18 @@ Today's data ecosystem is fragmented. Organizations rely on a wide array of tool - **AI Hallucinations**: When AI agents encounter conflicting or incomplete business logic across tools, they produce unreliable outputs grounded in inconsistent data definitions. - **Integration Debt**: Every new tool added to the stack requires custom integration work, creating a web of brittle, point-to-point connectors that are costly to maintain. -### How OSI Solves It +### How Apache Ossie Solves It -OSI addresses semantic fragmentation by providing: +Ossie addresses semantic fragmentation by providing: - **Single Source of Truth**: A unified specification for semantic and metric definitions that all tools can read and write, ensuring consistency across the entire data stack. -- **Native Interoperability**: A hub-and-spoke model where tools exchange semantic models through OSI as a common format — enabling direct platform-to-platform exchange without custom connectors. +- **Native Interoperability**: A hub-and-spoke model where tools exchange semantic models through Ossie as a common format — enabling direct platform-to-platform exchange without custom connectors. - **Trusted AI Grounding**: Consistent business logic and rich AI context annotations ensure that AI agents and LLMs can reliably interpret and query data. - **Reduced Total Cost of Ownership**: Automated model exchange eliminates manual reconciliation work and reduces the engineering effort needed to integrate new tools. ### Specification at a Glance -The OSI core specification (current version: **0.2.0.dev0**, latest released: **0.1.1**) defines a YAML-based format for describing semantic models. The key constructs are: +The Ossie core specification (current version: **0.2.0.dev0**, latest released: **0.1.1**) defines a YAML-based format for describing semantic models. The key constructs are: | Construct | Description | |-----------|-------------| @@ -44,13 +65,13 @@ For the full specification, see [core-spec/spec.md](../core-spec/spec.md). For v ### Participating Organizations -OSI is supported by a broad coalition of 50+ organizations across the data ecosystem, including: +Ossie is supported by a broad coalition of 50+ organizations across the data ecosystem, including: Alation, Anomalo, Atlan, AtScale, Bigeye, BlackRock, Blue Yonder, Carto, Cloudera, Coalesce, Collate, Collibra, Cogniti, Count, Credible, Cube, Databricks, DataHub, Denodo, dbt Labs, Dremio, Domo, Elementum AI, Firebolt, GoodData, Hex, Honeydew, Informatica, Instacart, JetBrains, Lightdash, Mistral AI, Omni, Oracle, Preset, Qlik, RelationalAI, Salesforce, Select Star, Sigma, Snowflake, Starburst Data, Strategy, Sundial, ThoughtSpot, and more. ### Converters -OSI converters follow a **hub-and-spoke** architecture: the OSI core specification acts as the central, vendor-neutral format, and each converter handles translation to or from a specific vendor format (e.g., Snowflake, dbt, Salesforce, Databricks). This avoids the need for point-to-point converters between every pair of vendors. +Ossie converters follow a **hub-and-spoke** architecture: the Ossie core specification acts as the central, vendor-neutral format, and each converter handles translation to or from a specific vendor format (e.g., Snowflake, dbt, Salesforce, Databricks). This avoids the need for point-to-point converters between every pair of vendors. For details on implementing a converter, see the [Converters Guide](../converters/index.md). @@ -58,8 +79,8 @@ For details on implementing a converter, see the [Converters Guide](../converter ## Project Governance -We wanted the OSI project to be a collaborative effort from the start. The purpose is to grow a community of developers, contributors, and users who are actively involved in shaping the OSI Specification as it moves along. -Open Semantic Interchange (OSI) project governance is inspired by the governance model from [The ASF](https://www.apache.org). +We wanted the Ossie project to be a collaborative effort from the start. The purpose is to grow a community of developers, contributors, and users who are actively involved in shaping the Ossie Specification as it moves along. +Apache Ossie project governance is inspired by the governance model from [The ASF](https://www.apache.org). ### The Apache Way @@ -82,47 +103,43 @@ All technical discussions, design decisions, and specification changes happen in The community collectively ensures that the specification and its associated tooling remain high quality, secure, and aligned with the project's mission of vendor-agnostic semantic interoperability. 6. **Independence** -The OSI project operates independently of any single vendor or organization. While contributors may be employed by companies that have a stake in semantic interoperability, the project's direction is determined by the community as a whole. +The Ossie project operates independently of any single vendor or organization. While contributors may be employed by companies that have a stake in semantic interoperability, the project's direction is determined by the community as a whole. + +### Governing Bodies -### Technical Governing Bodies +As an incubating project, Apache Ossie is governed by its **Podling Project Management Committee (PPMC)**, working alongside the project's **Mentors** and under the oversight of the **Apache Incubator PMC (IPMC)**. See [CONTRIBUTING.md](../CONTRIBUTING.md) for the full description of roles and process. -The **Technical Steering Committee (TSC)** is responsible for the overall technical direction of the OSI Specification. The TSC: +The **PPMC** is responsible for the overall direction and health of the podling. It: - Reviews and approves significant changes to the core specification -- Resolves technical disputes that cannot be settled through normal consensus +- Guides the roadmap for new specification features and extensions - Ensures backward compatibility and coherence across specification versions -- Oversees the roadmap for new specification features and extensions -- Has binding vote on the Specification Change vote - -New TSC members are elected by the TSC members. +- Casts binding votes and votes on committer/PPMC nominations and releases +- Resolves technical disputes that cannot be settled through normal consensus -The TSC members are: +New committers and PPMC members are nominated and voted on by the PPMC following the standard [ASF process](https://www.apache.org/dev/pmc.html). During incubation, releases are additionally approved by the Incubator PMC. -| Name | Affiliation | -|------|-------------| -| Khushboo Bhatia | Snowflake | -| Lior Ebel | Salesforce | -| Quigley Malcom | dbt Labs | -| JB Onofré | The ASF | +Current PPMC members and Mentors are listed on the [podling status page](https://incubator.apache.org/projects/ossie.html). -### Non-Governing Bodies +### Roles -- **Contributors**: Anyone who contributes to the project in any form — code, documentation, bug reports, specification feedback, or community support. Contributors do not have binding vote rights but are encouraged to participate in all discussions. -- **Committers**: Contributors who have earned write access to the repository through sustained contributions. Committers can merge pull requests and have binding votes on non-specification matters. -- **Specification Reviewers**: Community members with domain expertise (e.g., in BI, AI, data engineering) who review proposed specification changes for correctness, completeness, and practical applicability. +- **Contributors**: Anyone who contributes to the project in any form — code, documentation, bug reports, specification feedback, or community support. Contributor votes are non-binding, but everyone is encouraged to participate in all discussions and votes. +- **Committers**: Contributors who have earned write access to the repository through sustained contributions. Committers merge pull requests and have binding votes on the project's technical decisions. All committers have an ICLA on file. +- **PPMC Members**: Committers who also help steer the podling — growing the community, overseeing releases, and mentoring contributors. PPMC votes are binding. +- **Mentors**: Experienced ASF members assigned by the Incubator to guide the podling and shepherd release votes to the IPMC. -### Vote on Specification Changes +### Voting on Specification Changes -Changes to the OSI Specification follow a structured voting process: +Changes to the Apache Ossie specification follow the ASF voting model, held on the `dev@ossie.apache.org` mailing list: -1. **Proposal**: A specification change is submitted as a GitHub pull request with a clear description of the motivation, the change itself, and its impact on existing implementations. -2. **Discussion period**: The community has a minimum of 7 days to review and discuss the proposed change. Complex changes may require a longer review window. -3. **Vote**: Once the discussion period has elapsed, a vote is called. TSC members and committers cast votes using the following convention: - - **+1** (Yes): In favor of the change - - **0** (Abstain): No opinion or willing to go with the majority - - **-1** (Veto): Against the change — must be accompanied by a technical justification - The votes from the TSC members are considered as binding votes. -4. **Resolution**: A specification change passes with at least 2 binding +1 votes and no vetoes. A veto can only be overridden by addressing the stated concern or by a supermajority (two-thirds) vote of the TSC. +1. **Proposal**: Announce the change on `dev@ossie.apache.org` and open a GitHub pull request describing the motivation, the change, and its impact on existing implementations. +2. **Discussion period**: The community has a minimum of 7 days to review and discuss. Complex changes may require a longer window. +3. **Vote**: A `[VOTE]` thread is called. Voters cast: + - **+1**: In favor + - **0**: Abstain / no strong opinion + - **-1**: Veto — must include a technical justification; a valid veto is resolved only by addressing the stated concern + PPMC member votes are binding. +4. **Resolution**: A specification change passes with at least **three binding +1 votes** and no vetoes. ### Working Groups @@ -137,7 +154,7 @@ Community meetings are open to all participants and provide a forum for discussi - **Global Community Meetings**: Regular meetings open to all community members for cross-cutting discussions, roadmap reviews, and community announcements. - **Working Group Meetings**: Each working group holds its own meetings to drive focused progress on their specific area of the specification. -Meeting schedules, agendas, and notes are published on the [OSI website](https://open-semantic-interchange.org/) and the project's GitHub repository. All community members are welcome to attend and participate. +Meeting schedules, agendas, and notes are published on the [Ossie website](https://ossie.apache.org/) and the project's GitHub repository. All community members are welcome to attend and participate. **Google Calendar**: _link TBD_ @@ -147,7 +164,7 @@ Meeting schedules, agendas, and notes are published on the [OSI website](https:/ ### Hub-and-Spoke Model -OSI is designed around a hub-and-spoke architecture that dramatically simplifies the integration landscape. Instead of requiring every tool to build custom connectors to every other tool, OSI acts as the universal interchange format at the center. +Ossie is designed around a hub-and-spoke architecture that dramatically simplifies the integration landscape. Instead of requiring every tool to build custom connectors to every other tool, Ossie acts as the universal interchange format at the center. ``` ┌─────────────┐ @@ -155,7 +172,7 @@ OSI is designed around a hub-and-spoke architecture that dramatically simplifies └──────┬──────┘ │ ┌─────────────┐ ┌─────┴─────┐ ┌─────────────┐ - │ dbt ├────┤ OSI ├────┤ Salesforce │ + │ dbt ├────┤ Ossie ├────┤ Salesforce │ └─────────────┘ └─────┬─────┘ └─────────────┘ │ ┌──────┴──────┐ @@ -163,22 +180,22 @@ OSI is designed around a hub-and-spoke architecture that dramatically simplifies └─────────────┘ ``` -With N vendors, a point-to-point strategy would require **N×(N-1)** converters. With OSI as the hub, only **2×N** converters are needed (one import and one export per vendor), and interoperability with all other vendors comes for free. +With N vendors, a point-to-point strategy would require **N×(N-1)** converters. With Ossie as the hub, only **2×N** converters are needed (one import and one export per vendor), and interoperability with all other vendors comes for free. ### How It Flows -A typical OSI-based workflow looks like this: +A typical Ossie-based workflow looks like this: -1. **Author**: A semantic model is authored in one tool (e.g., dbt) or directly in the OSI YAML format. -2. **Import**: If authored in a vendor tool, the vendor's import converter translates it into an OSI model, preserving vendor-specific metadata in `custom_extensions`. -3. **Validate**: The OSI model is validated against the [JSON Schema](../core-spec/osi-schema.json) and the [validation script](../validation/validate.py) to ensure correctness. -4. **Exchange**: The OSI model is shared — via Git, a data catalog, or a sync API — with other teams and tools. -5. **Export**: Each consuming tool's export converter translates the OSI model into its native format, selecting the appropriate SQL dialect and applying vendor-specific extensions. -6. **Round-Trip**: When changes are made in a downstream tool, they can be imported back into the OSI model, preserving all metadata for lossless round-tripping. +1. **Author**: A semantic model is authored in one tool (e.g., dbt) or directly in the Ossie YAML format. +2. **Import**: If authored in a vendor tool, the vendor's import converter translates it into an Ossie model, preserving vendor-specific metadata in `custom_extensions`. +3. **Validate**: The Ossie model is validated against the [JSON Schema](../core-spec/osi-schema.json) and the [validation script](../validation/validate.py) to ensure correctness. +4. **Exchange**: The Ossie model is shared — via Git, a data catalog, or a sync API — with other teams and tools. +5. **Export**: Each consuming tool's export converter translates the Ossie model into its native format, selecting the appropriate SQL dialect and applying vendor-specific extensions. +6. **Round-Trip**: When changes are made in a downstream tool, they can be imported back into the Ossie model, preserving all metadata for lossless round-tripping. ### Multi-Dialect Expression System -A key architectural feature of OSI is its multi-dialect expression system. Fields and metrics can carry expressions in multiple SQL dialects simultaneously: +A key architectural feature of Ossie is its multi-dialect expression system. Fields and metrics can carry expressions in multiple SQL dialects simultaneously: ```yaml expression: @@ -202,32 +219,32 @@ This allows a single semantic model to be consumed natively by different platfor **What is a semantic model?** A semantic model is a structured description of business data that defines what datasets exist, what their fields mean, how datasets relate to each other, and what metrics (KPIs) can be computed from the data. It serves as a shared vocabulary between data producers and consumers — whether those consumers are humans using BI tools or AI agents generating queries. -**How is OSI different from existing standards?** -Most existing standards focus on data formats (e.g., Parquet, Arrow), query interfaces (e.g., ODBC, JDBC), or catalog metadata (e.g., Hive Metastore, OpenMetadata). OSI focuses specifically on the *semantic layer* — the business meaning, metric definitions, and relationships that sit on top of raw data. It is complementary to these other standards rather than a replacement. +**How is Ossie different from existing standards?** +Most existing standards focus on data formats (e.g., Parquet, Arrow), query interfaces (e.g., ODBC, JDBC), or catalog metadata (e.g., Hive Metastore, OpenMetadata). Ossie focuses specifically on the *semantic layer* — the business meaning, metric definitions, and relationships that sit on top of raw data. It is complementary to these other standards rather than a replacement. -**Is OSI tied to any specific vendor?** -No. OSI is vendor-agnostic by design. The specification is developed and governed by a community of contributors from many organizations. While vendor-specific metadata can be carried via `custom_extensions`, the core specification is neutral. +**Is Ossie tied to any specific vendor?** +No. Ossie is vendor-agnostic by design. The specification is developed and governed by a community of contributors from many organizations. While vendor-specific metadata can be carried via `custom_extensions`, the core specification is neutral. ### Adoption -**Can I use OSI with my existing BI tool?** -Yes, as long as a converter exists (or is built) for your tool. The hub-and-spoke model means that adding OSI support to a single tool gives it interoperability with every other OSI-compatible tool. Check the [Converters Guide](../converters/index.md) for currently supported vendors. +**Can I use Ossie with my existing BI tool?** +Yes, as long as a converter exists (or is built) for your tool. The hub-and-spoke model means that adding Ossie support to a single tool gives it interoperability with every other Ossie-compatible tool. Check the [Converters Guide](../converters/index.md) for currently supported vendors. **What if my vendor isn't supported yet?** You can contribute a converter. The [Converters Guide](../converters/index.md) provides a step-by-step guide for implementing import and export converters for new vendors. The community is happy to help with design reviews and testing. **Do I need to rewrite my existing semantic models?** -No. Import converters translate existing vendor-specific models into the OSI format automatically. Your existing models remain intact — OSI provides an additional interchange layer on top of them. +No. Import converters translate existing vendor-specific models into the Ossie format automatically. Your existing models remain intact — Ossie provides an additional interchange layer on top of them. -**How do I validate an OSI model?** +**How do I validate an Ossie model?** Use the [validation script](../validation/validate.py) included in the repository. It checks your model against the [JSON Schema](../core-spec/osi-schema.json), validates SQL expressions across dialects, and ensures referential integrity between datasets and relationships. ### Technical **Why YAML and not JSON?** -YAML is more human-readable and easier to author by hand, which is important for a specification that teams may edit directly. The OSI JSON Schema is available for programmatic validation, and converters can work with either format. +YAML is more human-readable and easier to author by hand, which is important for a specification that teams may edit directly. The Ossie JSON Schema is available for programmatic validation, and converters can work with either format. -**How does OSI handle vendor-specific features?** +**How does Ossie handle vendor-specific features?** Through `custom_extensions`. Each vendor can store arbitrary JSON metadata in extension blocks tagged with their vendor name. This metadata is preserved during round-trip conversions and ignored by tools that don't understand it — ensuring that no information is lost. **Can metrics reference multiple datasets?** @@ -242,7 +259,7 @@ The current specification supports `ANSI_SQL`, `SNOWFLAKE`, `DATABRICKS`, `MDX`, ### Specification Versioning -The OSI specification follows [Semantic Versioning](https://semver.org/) (SemVer): +The Ossie specification follows [Semantic Versioning](https://semver.org/) (SemVer): - **Major version** (e.g., 1.0.0 → 2.0.0): Breaking changes that are not backward compatible. Existing valid models may not be valid under the new version. Major version bumps are rare and go through an extended review and migration period. - **Minor version** (e.g., 0.1.0 → 0.2.0): New features or constructs added in a backward-compatible way. Existing valid models remain valid. @@ -264,7 +281,7 @@ Custom extensions (`custom_extensions`) are explicitly outside the scope of core ## Adoption Guide -A practical guide for organizations looking to adopt OSI. +A practical guide for organizations looking to adopt Ossie. ### Phase 1: Evaluate @@ -274,22 +291,22 @@ A practical guide for organizations looking to adopt OSI. ### Phase 2: Pilot -- **Start with one model**: Choose a well-understood semantic model (e.g., a core sales or finance model) and express it in the OSI format. +- **Start with one model**: Choose a well-understood semantic model (e.g., a core sales or finance model) and express it in the Ossie format. - **Validate**: Run the [validation script](../validation/validate.py) to ensure the model conforms to the specification. -- **Test round-tripping**: If converters are available, export the OSI model to your vendor format and compare with the original. Identify any gaps or lossy conversions. +- **Test round-tripping**: If converters are available, export the Ossie model to your vendor format and compare with the original. Identify any gaps or lossy conversions. - **Gather feedback**: Share the pilot results with your data team and collect feedback on the experience. ### Phase 3: Expand -- **Convert additional models**: Gradually bring more semantic models into the OSI format, prioritizing those shared across multiple tools. -- **Integrate into workflows**: Add OSI validation to your CI/CD pipeline. Store OSI models in version control alongside your data code. +- **Convert additional models**: Gradually bring more semantic models into the Ossie format, prioritizing those shared across multiple tools. +- **Integrate into workflows**: Add Ossie validation to your CI/CD pipeline. Store Ossie models in version control alongside your data code. - **Automate synchronization**: Use converters (and eventually the Sync API) to automate the propagation of semantic model changes across your tool ecosystem. ### Phase 4: Govern - **Establish ownership**: Define who owns each semantic model and who is responsible for approving changes. - **Implement review processes**: Use the voting and review processes described in this document (or your own governance model) to manage specification changes. -- **Monitor consistency**: Regularly validate that the semantic models consumed by each tool are in sync with the authoritative OSI model. +- **Monitor consistency**: Regularly validate that the semantic models consumed by each tool are in sync with the authoritative Ossie model. --- @@ -300,15 +317,15 @@ A practical guide for organizations looking to adopt OSI. | **Semantic Model** | A structured description of business data that defines datasets, fields, relationships, and metrics. It provides a shared vocabulary for interpreting data across tools and teams. | | **Dataset** | A logical representation of a business entity, typically corresponding to a fact table or dimension table in a data warehouse. | | **Field** | A row-level attribute within a dataset, used for grouping, filtering, or as part of metric expressions. Fields can be simple column references or computed expressions. | -| **Dimension** | A categorical attribute used to slice and filter data (e.g., region, product category, date). In OSI, dimensions are represented as fields with optional metadata such as `is_time`. | +| **Dimension** | A categorical attribute used to slice and filter data (e.g., region, product category, date). In Ossie, dimensions are represented as fields with optional metadata such as `is_time`. | | **Metric** | A quantitative measure computed by aggregating data across one or more datasets (e.g., total revenue, average order value). Metrics are defined at the semantic model level. | | **Relationship** | A foreign key connection between two datasets, defining how they can be joined. Relationships are always many-to-one (from the referencing dataset to the referenced dataset). | -| **Dialect** | A specific SQL or expression language variant (e.g., `ANSI_SQL`, `SNOWFLAKE`, `DATABRICKS`). OSI supports multiple dialects so expressions can be tailored to each platform. | -| **Custom Extension** | Vendor-specific metadata attached to any OSI construct as a JSON string. Extensions allow platforms to carry additional information without modifying the core specification. | -| **AI Context** | Optional annotations on any OSI construct (model, dataset, field, relationship, metric) that provide additional context for AI tools — including natural language instructions, synonyms, and example queries. | -| **Converter** | A tool that translates between the OSI format and a specific vendor's semantic model format. Converters come in pairs: import (vendor → OSI) and export (OSI → vendor). | -| **Hub-and-Spoke** | The architectural pattern used by OSI, where the specification acts as the central format (hub) and vendor converters act as spokes, avoiding the need for point-to-point integrations. | -| **Round-Trip Fidelity** | The ability to convert a model from one format to OSI and back without losing information. Achieved by preserving vendor-specific metadata in `custom_extensions`. | +| **Dialect** | A specific SQL or expression language variant (e.g., `ANSI_SQL`, `SNOWFLAKE`, `DATABRICKS`). Ossie supports multiple dialects so expressions can be tailored to each platform. | +| **Custom Extension** | Vendor-specific metadata attached to any Ossie construct as a JSON string. Extensions allow platforms to carry additional information without modifying the core specification. | +| **AI Context** | Optional annotations on any Ossie construct (model, dataset, field, relationship, metric) that provide additional context for AI tools — including natural language instructions, synonyms, and example queries. | +| **Converter** | A tool that translates between the Ossie format and a specific vendor's semantic model format. Converters come in pairs: import (vendor → Ossie) and export (Ossie → vendor). | +| **Hub-and-Spoke** | The architectural pattern used by Ossie, where the specification acts as the central format (hub) and vendor converters act as spokes, avoiding the need for point-to-point integrations. | +| **Round-Trip Fidelity** | The ability to convert a model from one format to Ossie and back without losing information. Achieved by preserving vendor-specific metadata in `custom_extensions`. | | **Fact Table** | A dataset that records business events or transactions (e.g., sales, clicks, shipments). Fact tables typically contain numeric measures and foreign keys to dimension tables. | | **Dimension Table** | A dataset that describes business entities referenced by fact tables (e.g., customers, products, dates). Dimension tables provide the context for analyzing facts. | | **Semantic Layer** | The abstraction layer between raw data and business users/tools. It defines the business meaning of data, standardizes metric calculations, and provides a consistent query interface. | @@ -317,8 +334,8 @@ A practical guide for organizations looking to adopt OSI. ## Related Resources -- **Website**: [open-semantic-interchange.org](https://open-semantic-interchange.org/) -- **GitHub**: [github.com/open-semantic-interchange](https://github.com/open-semantic-interchange) +- **Website**: [ossie.apache.org](https://ossie.apache.org/) +- **GitHub**: [github.com/apache/ossie](https://github.com/apache/ossie) - **Slack**: [join slack](https://join.slack.com/t/opensemanticx/shared_invite/zt-3yuad6c0h-MaoPgVSD1g9MEOf1_QeaiQ) - **Core Specification**: [core-spec/spec.md](../core-spec/spec.md) - **JSON Schema**: [core-spec/osi-schema.json](../core-spec/osi-schema.json) @@ -345,13 +362,13 @@ We welcome contributions from everyone — whether you are a developer, a data e 1. **Read the Specification**: Familiarize yourself with the [core specification](../core-spec/spec.md) to understand the semantic model format. 2. **Explore the Examples**: Review the [TPC-DS example](../examples/tpcds_semantic_model.yaml) to see a complete semantic model in practice. -3. **Join the Conversation**: Open or participate in [GitHub Issues](https://github.com/open-semantic-interchange) and Discussions to share ideas and feedback. +3. **Join the Conversation**: Open or participate in [GitHub Issues](https://github.com/apache/ossie) and Discussions to share ideas and feedback. 4. **Submit a Pull Request**: Fork the repository, make your changes, and submit a pull request. All contributions go through the standard review process described in the governance section above. 5. **Join the Slack workspace**: You can chat directly with the community by [joining Slack](https://join.slack.com/t/opensemanticx/shared_invite/zt-3yuad6c0h-MaoPgVSD1g9MEOf1_QeaiQ). ### Code of Conduct -All participants in the OSI community are expected to treat each other with respect and professionalism. We are committed to providing a welcoming and inclusive environment for everyone, regardless of background or experience level. +All participants in the Ossie community are expected to treat each other with respect and professionalism. We are committed to providing a welcoming and inclusive environment for everyone, regardless of background or experience level. --- diff --git a/docs/working_groups.md b/docs/working_groups.md index a21626b1..3e8a3a96 100644 --- a/docs/working_groups.md +++ b/docs/working_groups.md @@ -1,4 +1,23 @@ -# OSI Working Groups + + +# Apache Ossie Working Groups Join the community on Slack: [join.slack.com/t/opensemanticx](https://join.slack.com/t/opensemanticx/shared_invite/zt-3yuad6c0h-MaoPgVSD1g9MEOf1_QeaiQ) diff --git a/examples/flights.yaml b/examples/flights.yaml index 8c9d4a12..775709c9 100644 --- a/examples/flights.yaml +++ b/examples/flights.yaml @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + version: 0.2.0.dev0 name: flights description: Ontology for flight-related concepts and relationships diff --git a/examples/tpcds_semantic_model.yaml b/examples/tpcds_semantic_model.yaml index 7dd27851..c20f288e 100644 --- a/examples/tpcds_semantic_model.yaml +++ b/examples/tpcds_semantic_model.yaml @@ -1,7 +1,23 @@ # yaml-language-server: $schema=../core-spec/osi-schema.json +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. # TPC-DS Semantic Model Example -# This example demonstrates the OSI Core Metadata Spec using the TPC-DS benchmark schema +# This example demonstrates the Ossie Core Metadata Spec using the TPC-DS benchmark schema # TPC-DS is a decision support benchmark with a realistic retail business model version: "0.2.0.dev0" diff --git a/ontology/ontology.json b/ontology/ontology.json index 473f69b3..6488a895 100644 --- a/ontology/ontology.json +++ b/ontology/ontology.json @@ -1,8 +1,8 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://github.com/open-semantic-interchange/OSI/core-spec/osi-schema.json", - "title": "OSI Ontology Metadata Specification", - "description": "JSON Schema for validating OSI (Open Semantic Interoperability) ontology definitions", + "$id": "https://github.com/apache/ossie/core-spec/osi-schema.json", + "title": "Apache Ossie Ontology Metadata Specification", + "description": "JSON Schema for validating Apache Ossie ontology definitions", "type": "object", "properties": { "version": { @@ -19,7 +19,7 @@ "description": "Human-readable description" }, "ai_context": { - "$ref": "https://raw.githubusercontent.com/open-semantic-interchange/OSI/main/core-spec/osi-schema.json#/$defs/AIContext" + "$ref": "https://raw.githubusercontent.com/apache/ossie/main/core-spec/osi-schema.json#/$defs/AIContext" }, "ontology": { "type": "array", @@ -282,7 +282,7 @@ "description": "Human-readable description of this ontology map" }, "semantic_model": { - "$ref": "https://raw.githubusercontent.com/open-semantic-interchange/OSI/main/core-spec/osi-schema.json#/$defs/SemanticModel" + "$ref": "https://raw.githubusercontent.com/apache/ossie/main/core-spec/osi-schema.json#/$defs/SemanticModel" }, "concept_mappings": { "type": "array", diff --git a/ontology/ontology.md b/ontology/ontology.md index 921e061d..9e8aa7af 100644 --- a/ontology/ontology.md +++ b/ontology/ontology.md @@ -1,4 +1,23 @@ -# OSI - Ontology Specification + + +# Apache Ossie - Ontology Specification **Version:** 0.2.0.dev0 @@ -382,7 +401,7 @@ mappings that group by some concept. Each concept mapping declares how to populate a concept with objects and how to populate the relationships that group under that concept with links. These declarations are formed from patterns of expressions that -reference fields in a logical model that is declared using the OSI core semantic model spec. +reference fields in a logical model that is declared using the Ossie core semantic model spec. Concept mappings have the following schema: diff --git a/python/pyproject.toml b/python/pyproject.toml index baf099bb..d209859f 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -1,12 +1,33 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + [build-system] requires = ["hatchling"] build-backend = "hatchling.build" [project] -name = "osi-python" +name = "apache-ossie" version = "0.2.0.dev0" -description = "Python types for the Open Semantic Interchange (OSI) specification" +description = "Python types for the Apache Ossie semantic model specification" requires-python = ">=3.11" +classifiers = [ + "License :: OSI Approved :: Apache Software License", + "Programming Language :: Python :: 3", +] dependencies = [ "pydantic>=2.0", "PyYAML>=6.0", @@ -16,7 +37,7 @@ dependencies = [ text = "Apache-2.0" [tool.hatch.build.targets.wheel] -packages = ["src/osi"] +packages = ["src/ossie"] [tool.uv] dev-dependencies = [ diff --git a/python/src/osi/__init__.py b/python/src/osi/__init__.py deleted file mode 100644 index 00970fd6..00000000 --- a/python/src/osi/__init__.py +++ /dev/null @@ -1,33 +0,0 @@ -from osi.models import ( - OSIAIContext, - OSIAIContextObject, - OSICustomExtension, - OSIDataset, - OSIDialect, - OSIDialectExpression, - OSIDimension, - OSIDocument, - OSIExpression, - OSIField, - OSIMetric, - OSIRelationship, - OSISemanticModel, - OSIVendor, -) - -__all__ = [ - "OSIAIContext", - "OSIAIContextObject", - "OSICustomExtension", - "OSIDataset", - "OSIDialect", - "OSIDialectExpression", - "OSIDimension", - "OSIDocument", - "OSIExpression", - "OSIField", - "OSIMetric", - "OSIRelationship", - "OSISemanticModel", - "OSIVendor", -] diff --git a/python/src/ossie/__init__.py b/python/src/ossie/__init__.py new file mode 100644 index 00000000..cb105faf --- /dev/null +++ b/python/src/ossie/__init__.py @@ -0,0 +1,50 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +from ossie.models import ( + OSIAIContext, + OSIAIContextObject, + OSICustomExtension, + OSIDataset, + OSIDialect, + OSIDialectExpression, + OSIDimension, + OSIDocument, + OSIExpression, + OSIField, + OSIMetric, + OSIRelationship, + OSISemanticModel, + OSIVendor, +) + +__all__ = [ + "OSIAIContext", + "OSIAIContextObject", + "OSICustomExtension", + "OSIDataset", + "OSIDialect", + "OSIDialectExpression", + "OSIDimension", + "OSIDocument", + "OSIExpression", + "OSIField", + "OSIMetric", + "OSIRelationship", + "OSISemanticModel", + "OSIVendor", +] diff --git a/python/src/osi/models.py b/python/src/ossie/models.py similarity index 81% rename from python/src/osi/models.py rename to python/src/ossie/models.py index 766b6e0f..cc4aad59 100644 --- a/python/src/osi/models.py +++ b/python/src/ossie/models.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + from enum import Enum from typing import Any, Optional, Union @@ -144,7 +161,7 @@ class OSISemanticModel(BaseModel): class OSIDocument(BaseModel): - """Root OSI document.""" + """Root Ossie document.""" model_config = ConfigDict(frozen=True) @@ -154,10 +171,10 @@ class OSIDocument(BaseModel): semantic_model: list[OSISemanticModel] def to_osi_yaml(self, **kwargs: Any) -> str: - """Serialize to OSI-compliant YAML (uses field aliases and excludes None values).""" + """Serialize to Ossie-compliant YAML (uses field aliases and excludes None values).""" data = self.model_dump(by_alias=True, exclude_none=True, mode="json", **kwargs) return yaml.dump(data, default_flow_style=False, sort_keys=False, allow_unicode=True) def to_osi_json(self, **kwargs: Any) -> str: - """Serialize to OSI-compliant JSON (uses field aliases and excludes None values).""" + """Serialize to Ossie-compliant JSON (uses field aliases and excludes None values).""" return self.model_dump_json(by_alias=True, exclude_none=True, **kwargs) diff --git a/validation/validate.py b/validation/validate.py index a33cf76d..ad78d30c 100644 --- a/validation/validate.py +++ b/validation/validate.py @@ -1,8 +1,25 @@ #!/usr/bin/env python3 +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + """ -OSI Semantic Model Validator +Ossie Semantic Model Validator -Validates OSI YAML files against: +Validates Ossie YAML files against: 1. JSON Schema (structure, types, enums) 2. Unique names (datasets, fields, metrics, relationships) 3. Valid relationship references @@ -33,7 +50,7 @@ except ImportError: SQLGLOT_AVAILABLE = False -# Map OSI dialects to sqlglot dialects +# Map Ossie dialects to sqlglot dialects DIALECT_MAP = { "ANSI_SQL": None, # sqlglot default "SNOWFLAKE": "snowflake", @@ -199,7 +216,7 @@ def main(): args = sys.argv[1:] yaml_path = Path(args[0]) - schema_path = Path(__file__).parent.parent / "core-spec" / "osi-schema.json" + schema_path = Path(__file__).parent.parent / "core-spec" / "ossie-schema.json" if len(args) > 1: if len(args) == 3 and args[1] == "--schema": schema_path = Path(args[2]) From c7fc1169e12c9dbd45972418501d645d26f8ea7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?JB=20Onofr=C3=A9?= Date: Mon, 6 Jul 2026 08:58:24 +0200 Subject: [PATCH 02/13] Fix .asf.yaml --- .asf.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.asf.yaml b/.asf.yaml index 354410e6..b5e72a12 100644 --- a/.asf.yaml +++ b/.asf.yaml @@ -60,8 +60,6 @@ github: ghp_branch: gh-pages ghp_path: / - collaborators: - notifications: commits: commits@ossie.apache.org issues: issues@ossie.apache.org From 6442527c6c6dafc1f0a42d7e19ad22143ba2a078 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?JB=20Onofr=C3=A9?= Date: Tue, 7 Jul 2026 16:16:13 +0200 Subject: [PATCH 03/13] Remove branch protection for now --- .asf.yaml | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/.asf.yaml b/.asf.yaml index b5e72a12..40fa7e7a 100644 --- a/.asf.yaml +++ b/.asf.yaml @@ -41,16 +41,6 @@ github: review_drafts: false review_on_push: false - protected_branches: - main: - required_pull_request_reviews: - require_code_owner_reviews: false - dismiss_stale_reviews: true - require_last_push_approval: false - required_approving_review_count: 1 - - required_linear_history: true - features: wiki: false issues: true From 5b6f03d8a9a7e5794c32527aef4f804e2ae75833 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?JB=20Onofr=C3=A9?= Date: Tue, 7 Jul 2026 16:17:06 +0200 Subject: [PATCH 04/13] Remove gh-pages here as it's managed by ossie-website --- .asf.yaml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.asf.yaml b/.asf.yaml index 40fa7e7a..462fcf8d 100644 --- a/.asf.yaml +++ b/.asf.yaml @@ -47,15 +47,9 @@ github: projects: true discussions: true - ghp_branch: gh-pages - ghp_path: / - notifications: commits: commits@ossie.apache.org issues: issues@ossie.apache.org pullrequests: issues@ossie.apache.org discussions: dev@ossie.apache.org jira_options: link label - -publish: - whoami: asf-site From 3c88a83ef999891ec6beede66ca0c2e2b227890a Mon Sep 17 00:00:00 2001 From: Quigley Malcolm Date: Tue, 9 Jun 2026 16:12:29 -0500 Subject: [PATCH 05/13] feat(cli): scaffold go module with cobra command tree MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The OSI converters currently require manual environment setup and per-converter invocation. This lays the foundation for a unified CLI (osi) that will discover and invoke converters as plugins via a stdin/stdout JSON protocol. Creates cli/ at the repo root as a self-contained Go module (github.com/open-semantic-interchange/osi/cli). All commands are stubbed — no logic is wired in this commit. Design decisions: - Go chosen for static binary distribution; no runtime dependency for end users (brew/apt installable) - internal/osidir owns ~/.osi/plugins/ initialization and respects $OSI_PLUGIN_DIR for override; uses os.UserHomeDir() rather than $HOME for Windows portability - PersistentPreRunE on the root command ensures dir init runs before every subcommand; commented caveat that Cobra does not chain this automatically if a subcommand defines its own - MarkFlagsMutuallyExclusive("from", "to") handles the both-set case on osi convert; the neither-set case is validated manually in RunE since Cobra only guards against both being provided Build pipeline (Makefile, .goreleaser.yaml) and CI follow in separate commits. --- .gitignore | 4 ++++ cli/.tool-versions | 1 + cli/cmd/convert.go | 39 ++++++++++++++++++++++++++++++++++ cli/cmd/plugin/install.go | 22 +++++++++++++++++++ cli/cmd/plugin/list.go | 18 ++++++++++++++++ cli/cmd/plugin/plugin.go | 16 ++++++++++++++ cli/cmd/plugin/remove.go | 19 +++++++++++++++++ cli/cmd/root.go | 38 +++++++++++++++++++++++++++++++++ cli/cmd/validate.go | 24 +++++++++++++++++++++ cli/go.mod | 10 +++++++++ cli/go.sum | 10 +++++++++ cli/internal/osidir/osidir.go | 40 +++++++++++++++++++++++++++++++++++ cli/main.go | 21 ++++++++++++++++++ 13 files changed, 262 insertions(+) create mode 100644 cli/.tool-versions create mode 100644 cli/cmd/convert.go create mode 100644 cli/cmd/plugin/install.go create mode 100644 cli/cmd/plugin/list.go create mode 100644 cli/cmd/plugin/plugin.go create mode 100644 cli/cmd/plugin/remove.go create mode 100644 cli/cmd/root.go create mode 100644 cli/cmd/validate.go create mode 100644 cli/go.mod create mode 100644 cli/go.sum create mode 100644 cli/internal/osidir/osidir.go create mode 100644 cli/main.go diff --git a/.gitignore b/.gitignore index 63e36ff7..8cf6aa83 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,6 @@ **/__pycache__/ **/.venv/ + +# Go CLI +cli/dist/ +cli/osi diff --git a/cli/.tool-versions b/cli/.tool-versions new file mode 100644 index 00000000..05a23a62 --- /dev/null +++ b/cli/.tool-versions @@ -0,0 +1 @@ +golang 1.26.2 diff --git a/cli/cmd/convert.go b/cli/cmd/convert.go new file mode 100644 index 00000000..aa8797d7 --- /dev/null +++ b/cli/cmd/convert.go @@ -0,0 +1,39 @@ +package cmd + +import ( + "fmt" + + "github.com/spf13/cobra" +) + +var convertCmd = &cobra.Command{ + Use: "convert --from --input | --to --input ", + Short: "Convert a semantic model between OSI and a platform format", + RunE: runConvert, +} + +func init() { + convertCmd.Flags().String("from", "", "Source platform — converts platform → OSI") + convertCmd.Flags().String("to", "", "Target platform — converts OSI → platform") + convertCmd.Flags().StringP("input", "i", "", "Input file or directory path (required)") + convertCmd.Flags().StringP("output", "o", "", "Output directory path (default: ./osi-output//)") + convertCmd.Flags().String("plugin", "", "Path to plugin directory (bypasses name-based discovery)") + convertCmd.Flags().Int("timeout", 60, "Plugin invocation timeout in seconds") + convertCmd.Flags().String("max-input-size", "100MB", "Maximum total input size (e.g. 500MB)") + + _ = convertCmd.MarkFlagRequired("input") + convertCmd.MarkFlagsMutuallyExclusive("from", "to") +} + +func runConvert(cmd *cobra.Command, args []string) error { + from, _ := cmd.Flags().GetString("from") + to, _ := cmd.Flags().GetString("to") + + // MarkFlagsMutuallyExclusive handles the both-set case; handle neither here. + if from == "" && to == "" { + return fmt.Errorf("exactly one of --from or --to must be specified") + } + + fmt.Fprintln(cmd.OutOrStdout(), "not yet implemented") + return nil +} diff --git a/cli/cmd/plugin/install.go b/cli/cmd/plugin/install.go new file mode 100644 index 00000000..aabde2d1 --- /dev/null +++ b/cli/cmd/plugin/install.go @@ -0,0 +1,22 @@ +package plugin + +import ( + "fmt" + + "github.com/spf13/cobra" +) + +var installCmd = &cobra.Command{ + Use: "install [name[@version] | url]", + Short: "Install a plugin from the registry or a URL", + RunE: runPluginInstall, +} + +func init() { + installCmd.Flags().Bool("all", false, "Install the latest version of all registry plugins") +} + +func runPluginInstall(cmd *cobra.Command, args []string) error { + fmt.Fprintln(cmd.OutOrStdout(), "not yet implemented") + return nil +} diff --git a/cli/cmd/plugin/list.go b/cli/cmd/plugin/list.go new file mode 100644 index 00000000..f69e8bbe --- /dev/null +++ b/cli/cmd/plugin/list.go @@ -0,0 +1,18 @@ +package plugin + +import ( + "fmt" + + "github.com/spf13/cobra" +) + +var listCmd = &cobra.Command{ + Use: "list", + Short: "List installed and available plugins", + RunE: runPluginList, +} + +func runPluginList(cmd *cobra.Command, args []string) error { + fmt.Fprintln(cmd.OutOrStdout(), "not yet implemented") + return nil +} diff --git a/cli/cmd/plugin/plugin.go b/cli/cmd/plugin/plugin.go new file mode 100644 index 00000000..a57e103c --- /dev/null +++ b/cli/cmd/plugin/plugin.go @@ -0,0 +1,16 @@ +package plugin + +import "github.com/spf13/cobra" + +// Cmd is the parent "osi plugin" command. It is exported so cmd/root.go can +// register it. Invoking it bare prints help. +var Cmd = &cobra.Command{ + Use: "plugin", + Short: "Manage OSI plugins", +} + +func init() { + Cmd.AddCommand(listCmd) + Cmd.AddCommand(installCmd) + Cmd.AddCommand(removeCmd) +} diff --git a/cli/cmd/plugin/remove.go b/cli/cmd/plugin/remove.go new file mode 100644 index 00000000..1b886696 --- /dev/null +++ b/cli/cmd/plugin/remove.go @@ -0,0 +1,19 @@ +package plugin + +import ( + "fmt" + + "github.com/spf13/cobra" +) + +var removeCmd = &cobra.Command{ + Use: "remove ", + Short: "Remove an installed plugin", + Args: cobra.ExactArgs(1), + RunE: runPluginRemove, +} + +func runPluginRemove(cmd *cobra.Command, args []string) error { + fmt.Fprintln(cmd.OutOrStdout(), "not yet implemented") + return nil +} diff --git a/cli/cmd/root.go b/cli/cmd/root.go new file mode 100644 index 00000000..40d1fbc7 --- /dev/null +++ b/cli/cmd/root.go @@ -0,0 +1,38 @@ +package cmd + +import ( + "github.com/open-semantic-interchange/osi/cli/cmd/plugin" + "github.com/open-semantic-interchange/osi/cli/internal/osidir" + "github.com/spf13/cobra" +) + +var rootCmd = &cobra.Command{ + Use: "osi", + Short: "Open Semantic Interchange CLI", + Long: `osi is the command-line tool for the Open Semantic Interchange (OSI) project.`, + // NOTE: Cobra does NOT automatically chain PersistentPreRunE from parent to + // child. If any subcommand defines its own PersistentPreRunE or PreRunE, this + // function will not run for that subcommand. Future subcommands that define + // their own must call osidir.EnsurePluginDir() explicitly. + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + return osidir.EnsurePluginDir() + }, +} + +// Execute runs the root command. Called by main. +func Execute() error { + return rootCmd.Execute() +} + +// SetVersion sets the version string reported by `osi --version`. +func SetVersion(v string) { + rootCmd.Version = v +} + +func init() { + rootCmd.PersistentFlags().BoolP("verbose", "v", false, "Enable verbose output (shows plugin stderr)") + + rootCmd.AddCommand(convertCmd) + rootCmd.AddCommand(validateCmd) + rootCmd.AddCommand(plugin.Cmd) +} diff --git a/cli/cmd/validate.go b/cli/cmd/validate.go new file mode 100644 index 00000000..5ef9edc4 --- /dev/null +++ b/cli/cmd/validate.go @@ -0,0 +1,24 @@ +package cmd + +import ( + "fmt" + + "github.com/spf13/cobra" +) + +var validateCmd = &cobra.Command{ + Use: "validate [flags] [...]", + Short: "Validate one or more OSI YAML or JSON files", + Args: cobra.MinimumNArgs(1), + RunE: runValidate, +} + +func init() { + validateCmd.Flags().Bool("strict", false, "Promote warnings to errors") + validateCmd.Flags().String("output", "text", "Output format: text or json") +} + +func runValidate(cmd *cobra.Command, args []string) error { + fmt.Fprintln(cmd.OutOrStdout(), "not yet implemented") + return nil +} diff --git a/cli/go.mod b/cli/go.mod new file mode 100644 index 00000000..eabd4e0f --- /dev/null +++ b/cli/go.mod @@ -0,0 +1,10 @@ +module github.com/open-semantic-interchange/osi/cli + +go 1.22 + +require github.com/spf13/cobra v1.10.2 + +require ( + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/spf13/pflag v1.0.9 // indirect +) diff --git a/cli/go.sum b/cli/go.sum new file mode 100644 index 00000000..a6ee3e0f --- /dev/null +++ b/cli/go.sum @@ -0,0 +1,10 @@ +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= +github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= +github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY= +github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/cli/internal/osidir/osidir.go b/cli/internal/osidir/osidir.go new file mode 100644 index 00000000..cdb3f6bb --- /dev/null +++ b/cli/internal/osidir/osidir.go @@ -0,0 +1,40 @@ +package osidir + +import ( + "fmt" + "os" + "path/filepath" +) + +const ( + defaultOSIDir = ".osi" + pluginsSubdir = "plugins" + envVar = "OSI_PLUGIN_DIR" +) + +// PluginDir returns the resolved plugin directory path. +// It respects $OSI_PLUGIN_DIR if set, otherwise defaults to ~/.osi/plugins/. +func PluginDir() (string, error) { + if override := os.Getenv(envVar); override != "" { + return override, nil + } + // Use os.UserHomeDir rather than $HOME for Windows portability. + home, err := os.UserHomeDir() + if err != nil { + return "", fmt.Errorf("could not determine home directory: %w", err) + } + return filepath.Join(home, defaultOSIDir, pluginsSubdir), nil +} + +// EnsurePluginDir ensures the plugin directory exists, creating it if needed. +// It is safe to call multiple times — os.MkdirAll is idempotent. +func EnsurePluginDir() error { + dir, err := PluginDir() + if err != nil { + return err + } + if err := os.MkdirAll(dir, 0755); err != nil { + return fmt.Errorf("could not create plugin directory %s: %w", dir, err) + } + return nil +} diff --git a/cli/main.go b/cli/main.go new file mode 100644 index 00000000..9262cb51 --- /dev/null +++ b/cli/main.go @@ -0,0 +1,21 @@ +package main + +import ( + "os" + + "github.com/open-semantic-interchange/osi/cli/cmd" +) + +// version, commit, and date are set at build time by GoReleaser via ldflags. +var ( + version = "dev" + commit = "none" + date = "unknown" +) + +func main() { + cmd.SetVersion(version) + if err := cmd.Execute(); err != nil { + os.Exit(1) + } +} From fcefdc89f9cde720af3bdfbe48ef4a45b9be1491 Mon Sep 17 00:00:00 2001 From: Quigley Malcolm Date: Tue, 9 Jun 2026 16:33:56 -0500 Subject: [PATCH 06/13] chore(cli): add makefile and goreleaser build pipeline MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Enables local builds and cross-platform release artifacts for the OSI CLI. Makefile provides standard targets for day-to-day development: build, test, lint, release-dry-run, and clean. All targets are designed to run from within cli/. GoReleaser config targets linux/amd64, linux/arm64, darwin/amd64, darwin/arm64, and windows/amd64. windows/arm64 is excluded — no widely available CI runner and negligible current demand. CGO_ENABLED=0 is set for fully static binaries, enabling cross-compilation from any host without a C toolchain. Version, commit, and date are injected at build time via ldflags from the vars declared in main.go. --- cli/.goreleaser.yaml | 48 ++++++++++++++++++++++++++++++++++++++++++++ cli/Makefile | 19 ++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 cli/.goreleaser.yaml create mode 100644 cli/Makefile diff --git a/cli/.goreleaser.yaml b/cli/.goreleaser.yaml new file mode 100644 index 00000000..2351e42b --- /dev/null +++ b/cli/.goreleaser.yaml @@ -0,0 +1,48 @@ +version: 2 + +project_name: osi + +before: + hooks: + - go mod tidy + +builds: + - id: osi + main: . + binary: osi + goos: + - linux + - darwin + - windows + goarch: + - amd64 + - arm64 + ignore: + - goos: windows + goarch: arm64 + env: + - CGO_ENABLED=0 + ldflags: + - -s -w + - -X main.version={{.Version}} + - -X main.commit={{.Commit}} + - -X main.date={{.Date}} + +archives: + - id: osi + format: tar.gz + format_overrides: + - goos: windows + format: zip + name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}" + +checksum: + name_template: "checksums.txt" + +changelog: + sort: asc + filters: + exclude: + - "^docs:" + - "^test:" + - "^chore:" diff --git a/cli/Makefile b/cli/Makefile new file mode 100644 index 00000000..ed8de036 --- /dev/null +++ b/cli/Makefile @@ -0,0 +1,19 @@ +BINARY_NAME := osi +BUILD_DIR := dist + +.PHONY: build test lint release-dry-run clean + +build: + go build -o $(BUILD_DIR)/$(BINARY_NAME) . + +test: + go test ./... + +lint: + go vet ./... + +release-dry-run: + goreleaser release --snapshot --clean --config .goreleaser.yaml + +clean: + rm -rf $(BUILD_DIR) From ac660a089fc1550beff308c353c0f4ff767ebbca Mon Sep 17 00:00:00 2001 From: Quigley Malcolm Date: Tue, 9 Jun 2026 16:40:00 -0500 Subject: [PATCH 07/13] ci(cli): add github actions workflow for build and test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Runs go build, go vet, and go test on every push and pull request that touches cli/ or the workflow file itself. The paths filter prevents CLI changes from triggering unrelated workflows in this polyglot repo and vice versa. Go version is derived from go-version-file: cli/go.mod so the workflow automatically picks up any future toolchain bumps without a separate workflow edit. defaults.run.working-directory avoids repeating cd cli/ on every step. Cross-platform build testing is not included — CGO_ENABLED=0 means the linux/amd64 build is representative of all targets. GoReleaser snapshot builds are deferred to a future release workflow. --- .github/workflows/cli-ci.yml | 38 ++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 .github/workflows/cli-ci.yml diff --git a/.github/workflows/cli-ci.yml b/.github/workflows/cli-ci.yml new file mode 100644 index 00000000..e7290892 --- /dev/null +++ b/.github/workflows/cli-ci.yml @@ -0,0 +1,38 @@ +name: CLI CI + +on: + push: + paths: + - 'cli/**' + - '.github/workflows/cli-ci.yml' + pull_request: + paths: + - 'cli/**' + - '.github/workflows/cli-ci.yml' + +jobs: + build-and-test: + name: Build and test + runs-on: ubuntu-latest + defaults: + run: + working-directory: cli + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version-file: cli/go.mod + cache-dependency-path: cli/go.sum + + - name: go build + run: go build ./... + + - name: go vet + run: go vet ./... + + - name: go test + run: go test ./... From 5f269d2c28390085a49b8affcf593051bbca5b86 Mon Sep 17 00:00:00 2001 From: Quigley Malcolm Date: Tue, 9 Jun 2026 17:47:45 -0500 Subject: [PATCH 08/13] test(cli): add unit tests for internal/osidir Covers the only logic in F1 that warrants testing: $OSI_PLUGIN_DIR env var override, default path construction via os.UserHomeDir(), directory creation, and idempotent re-invocation of EnsurePluginDir. t.Setenv is used throughout so env var mutations are automatically restored after each test. t.TempDir is used for filesystem tests so no cleanup is needed and tests are safe to run in parallel. --- cli/internal/osidir/osidir_test.go | 65 ++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 cli/internal/osidir/osidir_test.go diff --git a/cli/internal/osidir/osidir_test.go b/cli/internal/osidir/osidir_test.go new file mode 100644 index 00000000..1d39193d --- /dev/null +++ b/cli/internal/osidir/osidir_test.go @@ -0,0 +1,65 @@ +package osidir + +import ( + "os" + "path/filepath" + "testing" +) + +func TestPluginDir_envOverride(t *testing.T) { + want := "/custom/plugin/dir" + t.Setenv(envVar, want) + + got, err := PluginDir() + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if got != want { + t.Errorf("got %q, want %q", got, want) + } +} + +func TestPluginDir_default(t *testing.T) { + t.Setenv(envVar, "") + + home, err := os.UserHomeDir() + if err != nil { + t.Fatalf("could not determine home dir: %v", err) + } + want := filepath.Join(home, defaultOSIDir, pluginsSubdir) + + got, err := PluginDir() + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if got != want { + t.Errorf("got %q, want %q", got, want) + } +} + +func TestEnsurePluginDir_createsDirectory(t *testing.T) { + tmp := t.TempDir() + target := filepath.Join(tmp, "plugins") + t.Setenv(envVar, target) + + if err := EnsurePluginDir(); err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if _, err := os.Stat(target); os.IsNotExist(err) { + t.Errorf("expected directory %q to exist, but it does not", target) + } +} + +func TestEnsurePluginDir_idempotent(t *testing.T) { + tmp := t.TempDir() + target := filepath.Join(tmp, "plugins") + t.Setenv(envVar, target) + + if err := EnsurePluginDir(); err != nil { + t.Fatalf("first call failed: %v", err) + } + if err := EnsurePluginDir(); err != nil { + t.Fatalf("second call failed: %v", err) + } +} From 780dcdb43703b503ef94c6b16b108b8d10b98b96 Mon Sep 17 00:00:00 2001 From: Quigley Malcolm Date: Wed, 10 Jun 2026 15:45:12 -0500 Subject: [PATCH 09/13] chore(cli): rename osi to ossie throughout cli scaffold MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Project branding has changed from OSI to OSSIE. Updates all user-facing and internal references in the CLI: - Binary name: osi → ossie - Go module path: .../osi/cli → .../ossie/cli - Default plugin directory: ~/.osi → ~/.ossie - Environment variable: OSI_PLUGIN_DIR → OSSIE_PLUGIN_DIR - GoReleaser project name and archive ids - All command descriptions and flag help text - Output directory default: ./osi-output → ./ossie-output --- cli/.goreleaser.yaml | 8 ++++---- cli/Makefile | 2 +- cli/cmd/convert.go | 8 ++++---- cli/cmd/plugin/plugin.go | 4 ++-- cli/cmd/root.go | 10 +++++----- cli/cmd/validate.go | 2 +- cli/go.mod | 2 +- cli/internal/osidir/osidir.go | 6 +++--- cli/main.go | 2 +- 9 files changed, 22 insertions(+), 22 deletions(-) diff --git a/cli/.goreleaser.yaml b/cli/.goreleaser.yaml index 2351e42b..3a0a17ba 100644 --- a/cli/.goreleaser.yaml +++ b/cli/.goreleaser.yaml @@ -1,15 +1,15 @@ version: 2 -project_name: osi +project_name: ossie before: hooks: - go mod tidy builds: - - id: osi + - id: ossie main: . - binary: osi + binary: ossie goos: - linux - darwin @@ -29,7 +29,7 @@ builds: - -X main.date={{.Date}} archives: - - id: osi + - id: ossie format: tar.gz format_overrides: - goos: windows diff --git a/cli/Makefile b/cli/Makefile index ed8de036..9ecbcb77 100644 --- a/cli/Makefile +++ b/cli/Makefile @@ -1,4 +1,4 @@ -BINARY_NAME := osi +BINARY_NAME := ossie BUILD_DIR := dist .PHONY: build test lint release-dry-run clean diff --git a/cli/cmd/convert.go b/cli/cmd/convert.go index aa8797d7..92722eea 100644 --- a/cli/cmd/convert.go +++ b/cli/cmd/convert.go @@ -8,15 +8,15 @@ import ( var convertCmd = &cobra.Command{ Use: "convert --from --input | --to --input ", - Short: "Convert a semantic model between OSI and a platform format", + Short: "Convert a semantic model between OSSIE and a platform format", RunE: runConvert, } func init() { - convertCmd.Flags().String("from", "", "Source platform — converts platform → OSI") - convertCmd.Flags().String("to", "", "Target platform — converts OSI → platform") + convertCmd.Flags().String("from", "", "Source platform — converts platform → OSSIE") + convertCmd.Flags().String("to", "", "Target platform — converts OSSIE → platform") convertCmd.Flags().StringP("input", "i", "", "Input file or directory path (required)") - convertCmd.Flags().StringP("output", "o", "", "Output directory path (default: ./osi-output//)") + convertCmd.Flags().StringP("output", "o", "", "Output directory path (default: ./ossie-output//)") convertCmd.Flags().String("plugin", "", "Path to plugin directory (bypasses name-based discovery)") convertCmd.Flags().Int("timeout", 60, "Plugin invocation timeout in seconds") convertCmd.Flags().String("max-input-size", "100MB", "Maximum total input size (e.g. 500MB)") diff --git a/cli/cmd/plugin/plugin.go b/cli/cmd/plugin/plugin.go index a57e103c..d996f133 100644 --- a/cli/cmd/plugin/plugin.go +++ b/cli/cmd/plugin/plugin.go @@ -2,11 +2,11 @@ package plugin import "github.com/spf13/cobra" -// Cmd is the parent "osi plugin" command. It is exported so cmd/root.go can +// Cmd is the parent "ossie plugin" command. It is exported so cmd/root.go can // register it. Invoking it bare prints help. var Cmd = &cobra.Command{ Use: "plugin", - Short: "Manage OSI plugins", + Short: "Manage OSSIE plugins", } func init() { diff --git a/cli/cmd/root.go b/cli/cmd/root.go index 40d1fbc7..45347bc8 100644 --- a/cli/cmd/root.go +++ b/cli/cmd/root.go @@ -1,15 +1,15 @@ package cmd import ( - "github.com/open-semantic-interchange/osi/cli/cmd/plugin" - "github.com/open-semantic-interchange/osi/cli/internal/osidir" + "github.com/open-semantic-interchange/ossie/cli/cmd/plugin" + "github.com/open-semantic-interchange/ossie/cli/internal/osidir" "github.com/spf13/cobra" ) var rootCmd = &cobra.Command{ - Use: "osi", + Use: "ossie", Short: "Open Semantic Interchange CLI", - Long: `osi is the command-line tool for the Open Semantic Interchange (OSI) project.`, + Long: `ossie is the command-line tool for the Open Semantic Interchange (OSSIE) project.`, // NOTE: Cobra does NOT automatically chain PersistentPreRunE from parent to // child. If any subcommand defines its own PersistentPreRunE or PreRunE, this // function will not run for that subcommand. Future subcommands that define @@ -24,7 +24,7 @@ func Execute() error { return rootCmd.Execute() } -// SetVersion sets the version string reported by `osi --version`. +// SetVersion sets the version string reported by `ossie --version`. func SetVersion(v string) { rootCmd.Version = v } diff --git a/cli/cmd/validate.go b/cli/cmd/validate.go index 5ef9edc4..b11d00ad 100644 --- a/cli/cmd/validate.go +++ b/cli/cmd/validate.go @@ -8,7 +8,7 @@ import ( var validateCmd = &cobra.Command{ Use: "validate [flags] [...]", - Short: "Validate one or more OSI YAML or JSON files", + Short: "Validate one or more OSSIE YAML or JSON files", Args: cobra.MinimumNArgs(1), RunE: runValidate, } diff --git a/cli/go.mod b/cli/go.mod index eabd4e0f..a8284253 100644 --- a/cli/go.mod +++ b/cli/go.mod @@ -1,4 +1,4 @@ -module github.com/open-semantic-interchange/osi/cli +module github.com/open-semantic-interchange/ossie/cli go 1.22 diff --git a/cli/internal/osidir/osidir.go b/cli/internal/osidir/osidir.go index cdb3f6bb..27fb7429 100644 --- a/cli/internal/osidir/osidir.go +++ b/cli/internal/osidir/osidir.go @@ -7,13 +7,13 @@ import ( ) const ( - defaultOSIDir = ".osi" + defaultOSIDir = ".ossie" pluginsSubdir = "plugins" - envVar = "OSI_PLUGIN_DIR" + envVar = "OSSIE_PLUGIN_DIR" ) // PluginDir returns the resolved plugin directory path. -// It respects $OSI_PLUGIN_DIR if set, otherwise defaults to ~/.osi/plugins/. +// It respects $OSSIE_PLUGIN_DIR if set, otherwise defaults to ~/.ossie/plugins/. func PluginDir() (string, error) { if override := os.Getenv(envVar); override != "" { return override, nil diff --git a/cli/main.go b/cli/main.go index 9262cb51..9d2a2028 100644 --- a/cli/main.go +++ b/cli/main.go @@ -3,7 +3,7 @@ package main import ( "os" - "github.com/open-semantic-interchange/osi/cli/cmd" + "github.com/open-semantic-interchange/ossie/cli/cmd" ) // version, commit, and date are set at build time by GoReleaser via ldflags. From 22e986e148d575bfa00585079242114ee1088386 Mon Sep 17 00:00:00 2001 From: Quigley Malcolm Date: Fri, 12 Jun 2026 10:04:42 -0500 Subject: [PATCH 10/13] chore(cli): rename internal osidir package to ossiedir MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Completes the OSI → OSSIE rename by updating the internal package directory, package declaration, and import reference in cmd/root.go. --- cli/cmd/root.go | 6 +++--- cli/internal/{osidir => ossiedir}/osidir.go | 2 +- cli/internal/{osidir => ossiedir}/osidir_test.go | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) rename cli/internal/{osidir => ossiedir}/osidir.go (98%) rename cli/internal/{osidir => ossiedir}/osidir_test.go (98%) diff --git a/cli/cmd/root.go b/cli/cmd/root.go index 45347bc8..772d87b1 100644 --- a/cli/cmd/root.go +++ b/cli/cmd/root.go @@ -2,7 +2,7 @@ package cmd import ( "github.com/open-semantic-interchange/ossie/cli/cmd/plugin" - "github.com/open-semantic-interchange/ossie/cli/internal/osidir" + "github.com/open-semantic-interchange/ossie/cli/internal/ossiedir" "github.com/spf13/cobra" ) @@ -13,9 +13,9 @@ var rootCmd = &cobra.Command{ // NOTE: Cobra does NOT automatically chain PersistentPreRunE from parent to // child. If any subcommand defines its own PersistentPreRunE or PreRunE, this // function will not run for that subcommand. Future subcommands that define - // their own must call osidir.EnsurePluginDir() explicitly. + // their own must call ossiedir.EnsurePluginDir() explicitly. PersistentPreRunE: func(cmd *cobra.Command, args []string) error { - return osidir.EnsurePluginDir() + return ossiedir.EnsurePluginDir() }, } diff --git a/cli/internal/osidir/osidir.go b/cli/internal/ossiedir/osidir.go similarity index 98% rename from cli/internal/osidir/osidir.go rename to cli/internal/ossiedir/osidir.go index 27fb7429..93960412 100644 --- a/cli/internal/osidir/osidir.go +++ b/cli/internal/ossiedir/osidir.go @@ -1,4 +1,4 @@ -package osidir +package ossiedir import ( "fmt" diff --git a/cli/internal/osidir/osidir_test.go b/cli/internal/ossiedir/osidir_test.go similarity index 98% rename from cli/internal/osidir/osidir_test.go rename to cli/internal/ossiedir/osidir_test.go index 1d39193d..0fe341c7 100644 --- a/cli/internal/osidir/osidir_test.go +++ b/cli/internal/ossiedir/osidir_test.go @@ -1,4 +1,4 @@ -package osidir +package ossiedir import ( "os" From 32955eb43e302fc25c2c46f486d8762d21ceeaf8 Mon Sep 17 00:00:00 2001 From: Quigley Malcolm Date: Tue, 23 Jun 2026 15:32:53 -0500 Subject: [PATCH 11/13] fix(ossiedir): rename defaultOSIDir constant to defaultOssieDir MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The osi → ossie rename missed this unexported constant. The value (.ossie) was already correct; only the identifier name was stale. --- cli/internal/ossiedir/osidir.go | 4 ++-- cli/internal/ossiedir/osidir_test.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cli/internal/ossiedir/osidir.go b/cli/internal/ossiedir/osidir.go index 93960412..051a8bd8 100644 --- a/cli/internal/ossiedir/osidir.go +++ b/cli/internal/ossiedir/osidir.go @@ -7,7 +7,7 @@ import ( ) const ( - defaultOSIDir = ".ossie" + defaultOssieDir = ".ossie" pluginsSubdir = "plugins" envVar = "OSSIE_PLUGIN_DIR" ) @@ -23,7 +23,7 @@ func PluginDir() (string, error) { if err != nil { return "", fmt.Errorf("could not determine home directory: %w", err) } - return filepath.Join(home, defaultOSIDir, pluginsSubdir), nil + return filepath.Join(home, defaultOssieDir, pluginsSubdir), nil } // EnsurePluginDir ensures the plugin directory exists, creating it if needed. diff --git a/cli/internal/ossiedir/osidir_test.go b/cli/internal/ossiedir/osidir_test.go index 0fe341c7..76a89813 100644 --- a/cli/internal/ossiedir/osidir_test.go +++ b/cli/internal/ossiedir/osidir_test.go @@ -26,7 +26,7 @@ func TestPluginDir_default(t *testing.T) { if err != nil { t.Fatalf("could not determine home dir: %v", err) } - want := filepath.Join(home, defaultOSIDir, pluginsSubdir) + want := filepath.Join(home, defaultOssieDir, pluginsSubdir) got, err := PluginDir() if err != nil { From c707545a6c93d432bb2f087fc6b8e92830b0227a Mon Sep 17 00:00:00 2001 From: Quigley Malcolm Date: Tue, 23 Jun 2026 15:33:28 -0500 Subject: [PATCH 12/13] chore(cli): fix .gitignore to ignore cli/ossie build artifact MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The osi → ossie rename missed the gitignore entry, so the local build artifact cli/ossie would no longer be ignored by git. --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 8cf6aa83..5f39ab37 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,4 @@ # Go CLI cli/dist/ -cli/osi +cli/ossie From 7f9fdb75510e69259b4e4b4b7e8a8ad856060347 Mon Sep 17 00:00:00 2001 From: Quigley Malcolm Date: Wed, 24 Jun 2026 16:11:52 -0500 Subject: [PATCH 13/13] chore(ossiedir): rename osidir.go and osidir_test.go to ossiedir MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit File names were inconsistent with the package directory name (ossiedir/). Pure rename — no code changes. --- cli/internal/ossiedir/{osidir.go => ossiedir.go} | 0 cli/internal/ossiedir/{osidir_test.go => ossiedir_test.go} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename cli/internal/ossiedir/{osidir.go => ossiedir.go} (100%) rename cli/internal/ossiedir/{osidir_test.go => ossiedir_test.go} (100%) diff --git a/cli/internal/ossiedir/osidir.go b/cli/internal/ossiedir/ossiedir.go similarity index 100% rename from cli/internal/ossiedir/osidir.go rename to cli/internal/ossiedir/ossiedir.go diff --git a/cli/internal/ossiedir/osidir_test.go b/cli/internal/ossiedir/ossiedir_test.go similarity index 100% rename from cli/internal/ossiedir/osidir_test.go rename to cli/internal/ossiedir/ossiedir_test.go