From f4bf6bb57ab52145a896ad70b386701b9ec6d910 Mon Sep 17 00:00:00 2001 From: Dan Wolfson Date: Thu, 18 Jul 2024 10:32:35 -0500 Subject: [PATCH] Updated for working versions of images etc. Signed-off-by: Dan Wolfson --- CODEOWNERS | 2 +- README.md | 79 +- build.gradle | 279 ------- config/checkstyle/checkstyle.xml | 19 - config/checkstyle/java.header | 2 - egeria-platform-compose/README.md | 83 ++ egeria-platform-compose/egeria-platform.yaml | 58 ++ egeria-platform-jupyter-compose/.env | 1 + .../Dockerfile-jupyter | 42 + egeria-platform-jupyter-compose/README.md | 72 ++ egeria-platform-jupyter-compose/Start-Here.md | 16 + .../application.properties | 226 ++++++ .../before-notebook.d/config-coco.sh | 18 + .../egeria-platform-jupyter-compose.yaml | 95 +++ egeria-platform-postgres-compose/README.md | 83 ++ .../init_egeria_observations.sql | 126 +++ .../egeria-platform-postgres.yaml | 71 ++ gradle.properties | 30 - gradle/wrapper/gradle-wrapper.jar | Bin 61608 -> 0 bytes gradle/wrapper/gradle-wrapper.properties | 6 - gradlew | 244 ------ gradlew.bat | 92 --- pyproject.toml | 18 + settings.gradle | 19 - .../odpi/openmetadata/placeholder/Sample.java | 10 - .../openmetadata/placeholder/SampleTest.java | 21 - workspaces/Survey & Explore.ipynb | 76 ++ workspaces/Untitled.ipynb | 97 +++ workspaces/experiments.ipynb | 734 ++++++++++++++++++ workspaces/starter.jupyterlab-workspace | 1 + workspaces/uc_oss_catalog_viewer.py | 167 ++++ 31 files changed, 2057 insertions(+), 730 deletions(-) delete mode 100644 build.gradle delete mode 100644 config/checkstyle/checkstyle.xml delete mode 100644 config/checkstyle/java.header create mode 100644 egeria-platform-compose/README.md create mode 100644 egeria-platform-compose/egeria-platform.yaml create mode 100644 egeria-platform-jupyter-compose/.env create mode 100644 egeria-platform-jupyter-compose/Dockerfile-jupyter create mode 100644 egeria-platform-jupyter-compose/README.md create mode 100644 egeria-platform-jupyter-compose/Start-Here.md create mode 100755 egeria-platform-jupyter-compose/application.properties create mode 100755 egeria-platform-jupyter-compose/before-notebook.d/config-coco.sh create mode 100644 egeria-platform-jupyter-compose/egeria-platform-jupyter-compose.yaml create mode 100644 egeria-platform-postgres-compose/README.md create mode 100644 egeria-platform-postgres-compose/docker-entrypoint-initdb.d/init_egeria_observations.sql create mode 100644 egeria-platform-postgres-compose/egeria-platform-postgres.yaml delete mode 100644 gradle.properties delete mode 100644 gradle/wrapper/gradle-wrapper.jar delete mode 100644 gradle/wrapper/gradle-wrapper.properties delete mode 100755 gradlew delete mode 100644 gradlew.bat create mode 100644 pyproject.toml delete mode 100644 settings.gradle delete mode 100644 src/main/java/org/odpi/openmetadata/placeholder/Sample.java delete mode 100644 src/test/java/org/odpi/openmetadata/placeholder/SampleTest.java create mode 100644 workspaces/Survey & Explore.ipynb create mode 100644 workspaces/Untitled.ipynb create mode 100644 workspaces/experiments.ipynb create mode 100644 workspaces/starter.jupyterlab-workspace create mode 100644 workspaces/uc_oss_catalog_viewer.py diff --git a/CODEOWNERS b/CODEOWNERS index 646fa26..500e441 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -7,4 +7,4 @@ # Top level # TODO: Update with new owners of files added -/* @mandy-chessell @planetf1 +/* @mandy-chessell @dwolfson diff --git a/README.md b/README.md index d391d4b..c0f837c 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,77 @@ - - +# Overview +This directory contains sample Docker Compose scripts to support the deployment of Egeria for experimentation, +development, and learning. Rather than having to install Egeria, prerequisites and tools separately, these scripts make +it easy to get a stack running quickly. +These are not meant for production use. Please see the [Planning Guide](https://egeria-project.org/guides/planning/) +for more information about designing Egeria deployments. The Egeria community has also created samples for other +deployment styles, such as Cloud Native approaches and the use of Helm charts to configure Kubernetes clusters. These +options may be better starting points for production deployments - depending upon your requirements. +Please feel free to engage with the community on our slack channel - we'd love your feedback and participation. -# Egeria - +For a quick and simple environment to explore some of Egeria's base capabilities, the **egeria-platform.yml** Docker Compose +deployment may be a good starting point. Once this script executes successfully, you will have two docker containers running. One for the Egeria platform and one for Kafka. With this running configuration, you can work with any of Egeria's standard interfaces - java APIs, python APIs, or just plain RESTful http calls - and of course, to make use of tools and interfaces that have been built using these APIs. -See https://egeria-project.org for the main Egeria Documentation. +The set of **Docker Compose** configurations will grow and evolve over time to cover additional scenarios. -This is a new repository that we are currently setting up. +# Contents -A description and more content should be added here soon. +Our first docker compose script is called **egeria-platform.yml**. After running this script, you will have a running environment +that consists of a single Egeria runtime platform and the Apache Kafka event system. Information about configuring +Egeria can be found at [Configuring Egeria](https://egeria-project.org/guides/admin/configuring-the-omag-server-platform/). +We use standard, out-of-the-box configurations for both - a minimal amount of configuration for: + +## Egeria Platform - Default Configuration +We use the Egeria platform docker image - [egeria-platform](https://hub.docker.com/r/odpi/egeria-platform). + +* Port - By default the platform uses port 9443 and exposes this port to the host environment, This means that Egeria requests +can be made to platform URL **https://localhost:9443** or, if your environment is configured to support it, it can use +the domain name of your host machine. +* SSL - By default strict SSL is set to false +* Auto-Started Servers - by default a useful set of Egeria Open Metadata and Governance (OMAG) servers are pre-installed +and started when the Egeria platform is started. A description of these servers can be found at [sample configs](https://github.com/odpi/egeria/tree/main/open-metadata-resources/open-metadata-deployment/sample-configs): + + * simple-metadata-store + * active-metadata-store + * engine-host + * integration-daemon + * view-server + +* Content Packs - pre-constructed information sets that can be used to configure Egeria and pre-load metadata, reference data and glossary data. See [Content Packs](https://egeria-project.org/content-packs/). +* Out-of-the-box Connectors - descriptions of the integration connectors can be found at [Integration Connectors](https://egeria-project.org/connectors/). + +## Kafka - configured for Egeria +We use the bitnami/kafka image described at [kafka](https://hub.docker.com/r/bitnami/kafka) +* Port - We use the default port of 9092 for Kafka. This port is also exposed in the host environment. Changing this port also requires corresponding changes to the Egeria configuration. +* Other configuration can be seen in the *egeria-platform.yaml* file. + +# Usage +Follow these steps to use Docker Compose. + +1. Install and Configure Docker and Docker Compose. + * Docker and Docker compose must be installed and running - see https://docs.docker.com/install/ + * Configure docker with at least 6GB memory +2. Download the [**egeria-platform.yaml**](https://raw.githubusercontent.com/odpi/egeria/main/open-metadata-resources/open-metadata-deployment/docker-compose/egeria-platform-compose/egeria-platform.yaml) +3. Run the docker compose script from a terminal window in the directory where you downloaded `egeria-platform.yaml`. At the command line issue: + + `docker compose -f egeria-platform.yaml up` + +This will download the docker images for Kafka and Egeria, then create and start the two containers. Both kafka and Egeria will then automatically configure themselves. For Egeria, this means not only starting up the initial set of servers, but then loading the **CoreContentPack.omarchive** into the metadata repository, and then configuring all the servers. This can take several minutes the first time the containers are created. Subsequent startups will be much faster. + +4. Using either the **docker desktop** application or the docker command line you can see the two new containers running. To do this with the docker command line, you can issue: + +`docker ps` + +5. The environment is ready to be used. + +6. You can control the containers with docker compose commands - see [docker compose](https://docs.docker.com/reference/cli/docker/compose/). These commands can be used to administer and use the docker containers. + +## Next Steps + +Now that your Egeria environment is running and configured it is waiting for you to make requests. +Some tutorials for working with Egeria can be found at [Tutorials](https://egeria-project.org/education/tutorials/). For those that want to try the new python client, you can find a quick introduction at [pyegeria](https://getting-started-with-egeria.pdr-associates.com/recipe-6-charming-python.html). + +As always, your feedback and participation are welcome. + + +License: CC BY 4.0, Copyright Contributors to the ODPi Egeria project. \ No newline at end of file diff --git a/build.gradle b/build.gradle deleted file mode 100644 index 5624f37..0000000 --- a/build.gradle +++ /dev/null @@ -1,279 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * Copyright Contributors to the ODPi Egeria project. - */ - -plugins { - id 'java-library' - id "com.github.johnrengelman.shadow" version "8.1.0" - id 'idea' - id 'maven-publish' - // Checks for unnecessary dependencies - id 'com.autonomousapps.dependency-analysis' version "1.19.0" - // helps resolve log implementation clashes - id 'dev.jacomet.logging-capabilities' version "0.11.0" - // This plugin helps resolve jakarta/javax dev.jacomet.logging-capabilities - id 'org.gradlex.java-ecosystem-capabilities' version "1.1" - // copyright checks - id 'checkstyle' - -} - -// Mostly java, so default to this for now -apply plugin: 'java' -apply plugin: 'jacoco' -apply plugin: 'org.gradlex.java-ecosystem-capabilities' -apply plugin: 'dev.jacomet.logging-capabilities' -// As we've migrated from maven - we'll assume all submodules publish directly to maven -apply plugin: 'maven-publish' - -if (System.getenv("CI")) { - apply plugin: 'signing' -} - -repositories { - mavenCentral() - maven { url("https://oss.sonatype.org/content/repositories/snapshots") } -} - -// ensures we pick up the very latest snapshots when built -configurations.all { - // check for updates every build - resolutionStrategy.cacheChangingModulesFor 0, 'seconds' -} - -// TODO: Specify group, description, version of new code being built. The name is in settings.gradle -group = 'org.odpi.egeria' -version = '1.0-SNAPSHOT' -description = 'New code for Egeria' - -// TODO: Specify Dependency versions as variables -ext { - egeriaVersion = '4.0-SNAPSHOT' -} - -// For a single module, add here. For multiple modules, refactor and use constraints (see main egeria build.gradle for an example) - - -dependencies { - - // This will set constraints so that dependencies here follow egeria versions - implementation platform("org.odpi.egeria:egeria:${egeriaVersion}") - - // Many of these dependencies are in the platform already - in which case leave version empty - // Only used for build - already present in server chassis at runtime - similar to 'provided' with maven - compileOnly "org.odpi.egeria:audit-log-framework" - - // Needed for build and run - also found in the platform - compileOnly "org.slf4j:slf4j-api" - - // Only needed to compile test code - testImplementation "com.fasterxml.jackson.core:jackson-annotations" - testImplementation "org.junit.jupiter:junit-jupiter-api" - testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine" - -} - - -// Testing -test { - useJUnitPlatform { - includeEngines 'junit-jupiter' - } - testLogging { - events "passed", "skipped", "failed" - } - reports { - html.required = true - } -} - - -// Maven Central (technically sonatype oss) requires we distribute source and javadoc. -java { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - withJavadocJar() - withSourcesJar() -} - -// More Java language settings -tasks.withType(JavaCompile) { - options.encoding = 'UTF-8' - sourceCompatibility = "17" - targetCompatibility = "17" - options.incremental = true - options.fork = false - options.failOnError = true - options.compilerArgs << '-Xlint:unchecked' << '-Xlint:deprecation' -} - -// For later java versions this is recommended - keep conditional in case we want to build on 8 -javadoc { - if (JavaVersion.current().isJava9Compatible()) { - options.addBooleanOption('html5', true) - } -} - -// code coverage -jacoco { - toolVersion = "0.8.8" -} - -// Can remove the shadowJar definitions if a jar with dependencies is not needed -// compileonly content won't be included, so even an uber jar should not contain egeria dependencies already in chassis. -shadowJar { - archiveClassifier = 'jar-with-dependencies' -} - -// We want to built a jar with dependencies in a normal build, so to avoid specifying task explicitly -build.dependsOn shadowJar - - -// We only have a single artifact for now - this additional metadata is -// required for publishing to maven central. Only doing signing in 'CI' -publishing { - publications { - connector(MavenPublication) { - from components.java - pom { - url = 'http://egeria.odpi.org' - licenses { - // Code - license { - name = 'The Apache License, Version 2.0' - url = 'http://www.apache.org/licenses/LICENSE-2.0.txt' - } - // Docs - license { - name = 'Creative Commons Attribution 4.0 International (CC BY 4.0)' - url = 'https://creativecommons.org/licenses/by/4.0' - } - } - developers { - developer { - id = 'planetf1' - name = 'Nigel Jones' - email = 'nigel.l.jones+git@gmail.com' - } - } - scm { - // TODO: Update references to git source for this particular repository - connection = 'scm:git:git://github.com/odpi/egeria-template-newrepo.git' - developerConnection = 'scm:git:ssh://github.com/odpi/egeria/egeria-template-newrepo.git' - url = 'http://github.com/odpi/egeria-template-newrepo/' - } - } - // The maven artifact name comes from settings.gradle, if not found there defaults to directory name - pom.withXml { - asNode().appendNode('description', "${project.description}") - } - } - } - - // Release versions get pushed to staging area on maven central, snapshots to snapshot repo - // Secrets for credentials - if (System.getenv("CI")) { - repositories { - maven { - name = 'OSSRH' - def releasesRepoUrl = 'https://oss.sonatype.org/service/local/staging/deploy/maven2' - def snapshotsRepoUrl = 'https://oss.sonatype.org/content/repositories/snapshots' - url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl - // User token (under profile) on oss.sonatype.org - credentials { - username = System.getenv("OSSRH_USERNAME") - password = System.getenv("OSSRH_TOKEN") - } - } - } - } -} - -// To publish to ossrh we need to sign the artifacts - only in CI -if (System.getenv("CI")) { - signing { - // This is the publication to sign - sign publishing.publications.connector - // gpg --export-secret-keys myemal@gmail.com | base64 - def signingKey = System.getenv("OSSRH_GPG_PRIVATE_KEY") - // Passphrase for key - def signingPassword = System.getenv("OSSRH_GPG_PASSPHRASE") - // public key id (last 8 characters only) - note keys also need uploading to all the main registries - def signingKeyId = System.getenv("OSSRH_GPG_KEYID") - // We use these values from secrets rather than gradle.properties - useInMemoryPgpKeys(signingKeyId, signingKey, signingPassword) - } -} - -/* - * Additional useful tasks - */ -task printAllDependencies(type: DependencyReportTask) {} -task printSubDependencies(type: DependencyReportTask) {} -task findDependency(type: DependencyInsightReportTask) {} - - -/* - * Configuration for sub projects only - */ -subprojects { - // All tasks currently will run under allProjects - which includes root. -} - -/* - * Additional aggregate tasks run only at parent - */ - -// Jacoco reporting -- from gradle docs -task codeCoverageReport(type: JacocoReport) { - // Gather execution data from all subprojects - executionData fileTree(project.rootDir.absolutePath).include("**/build/jacoco/*.exec") - - // Add all relevant sourcesets from the subprojects - subprojects.each { - sourceSets it.sourceSets.main - } - - // enable the different report types (html, xml, csv) - reports { - // xml is usually used to integrate code coverage with - // other tools like SonarQube, Coveralls or Codecov - xml.required = true - - // HTML reports can be used to see code coverage - // without any external tools - html.required = true - csv.required = true - } - -} -// always run the tests before generating the report -codeCoverageReport.dependsOn { - subprojects*.test -} - -// Dependency checking - see https://github.com/autonomousapps/dependency-analysis-android-gradle-plugin -dependencyAnalysis { - issues { - all { - onAny { - severity('fail') - } - onUnusedDependencies { - exclude("junit:junit", "org.junit.jupiter:junit-jupiter-api", "org.junit.jupiter:junit-jupiter-engine", "com.fasterxml.jackson.core:jackson-annotations") - } - } - } -} - -// Checking of javaheaders for license & copyright -checkstyle { - toolVersion = '10.7.0' - showViolations = true -} - -// Always run dependency check for every regular build -build.dependsOn("buildHealth") - - diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml deleted file mode 100644 index 5b27a02..0000000 --- a/config/checkstyle/checkstyle.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/config/checkstyle/java.header b/config/checkstyle/java.header deleted file mode 100644 index 2ce66e0..0000000 --- a/config/checkstyle/java.header +++ /dev/null @@ -1,2 +0,0 @@ - ^/* SPDX-License-Identifier: Apache-2.0 */$ - ^/* Copyright Contributors to the ODPi Egeria project. */$ diff --git a/egeria-platform-compose/README.md b/egeria-platform-compose/README.md new file mode 100644 index 0000000..4750b4f --- /dev/null +++ b/egeria-platform-compose/README.md @@ -0,0 +1,83 @@ +# Overview +This directory contains sample Docker Compose scripts to support the deployment of Egeria for experimentation, +development, and learning. Rather than having to install Egeria, prerequisites and tools separately, these scripts make +it easy to get a stack running quickly. +These are not meant for production use. Please see the [Planning Guide](https://egeria-project.org/guides/planning/) +for more information about designing Egeria deployments. The Egeria community has also created samples for other +deployment styles, such as Cloud Native approaches and the use of Helm charts to configure Kubernetes clusters. These +options may be better starting points for production deployments - depending upon your requirements. +Please feel free to engage with the community on our slack channel - we'd love your feedback and participation. + + +For a quick and simple environment to explore some of Egeria's base capabilities, the **egeria-platform.yml** Docker Compose +deployment may be a good starting point. Once this script executes successfully, you will have two docker containers running. One for the Egeria platform and one for Kafka. With this running configuration, you can work with any of Egeria's standard interfaces - java APIs, python APIs, or just plain RESTful http calls - and of course, to make use of tools and interfaces that have been built using these APIs. + +The set of **Docker Compose** configurations will grow and evolve over time to cover additional scenarios. For example, +the folder `egeria-platform-postgres-compose` contains a docker compose configuration that adds a Postgres +database along with the Egeria OMAG platform and Kafka servers. This sets the stage emerging scenarios that +utilize a relational database to collect Egeria derived information such as Audit logs for additional analysis and dashboarding. +Please see the embedded README.md files for more details. + +# Contents + +* egeria-platform-compose - a basic Egeria OMAG platform standalone deployment that includes Kafka as well as Egeria. +* egeria-platform-postgres-compose - deploys and configures a Postgres relational database as well as Kafka and Egeria. + +Our first docker compose script is called **egeria-platform.yml**. After running this script, you will have a running environment +that consists of a single Egeria runtime platform and the Apache Kafka event system. Information about configuring +Egeria can be found at [Configuring Egeria](https://egeria-project.org/guides/admin/configuring-the-omag-server-platform/). +We use standard, out-of-the-box configurations for both - a minimal amount of configuration for: + +## Egeria Platform - Default Configuration +We use the Egeria platform docker image - [egeria-platform](https://hub.docker.com/r/odpi/egeria-platform). + +* Port - By default the platform uses port 9443 and exposes this port to the host environment, This means that Egeria requests +can be made to platform URL **https://localhost:9443** or, if your environment is configured to support it, it can use +the domain name of your host machine. +* SSL - By default strict SSL is set to false +* Auto-Started Servers - by default a useful set of Egeria Operational Metadata and Governance (OMAG) servers are pre-installed +and started when the Egeria platform is started. The pre-configured and started servers are: + * simple-metadata-store + * active-metadata-store + * engine-host + * integration-daemon + * view-server +A description of these servers can be found at [sample configs](open-metadata-resources/open-metadata-deployment/sample-configs/README.md) +* Content Packs - pre-constructed information sets that can be used to configure Egeria and pre-load metadata, reference data and glossary data. See [Content Packs](https://egeria-project.org/content-packs/). +* Out-of-the-box Connectors - descriptions of the integration connectors can be found at [Integration Connectors](https://egeria-project.org/connectors/). + +## Kafka - configured for Egeria +We use the bitnami/kafka image described at [kafka](https://hub.docker.com/r/bitnami/kafka) +* Port - We use the default port of 9092 for Kafka. This port is also exposed in the host environment. Changing this port also requires corresponding changes to the Egeria configuration. +* Other configuration can be seen in the *egeria-platform.yaml* file. + +# Usage +Follow these steps to use Docker Compose. + +1. Install and Configure Docker and Docker Compose. + * Docker and Docker compose must be installed and running - see https://docs.docker.com/install/ + * Configure docker with at least 6GB memory +2. Download the [**egeria-platform.yaml**](https://raw.githubusercontent.com/odpi/egeria/main/open-metadata-resources/open-metadata-deployment/docker-compose/egeria-platform-compose/egeria-platform.yaml) +3. Run the docker compose script from a terminal window in the directory where you downloaded `egeria-platform.yaml`. At the command line issue: + + `docker compose -f egeria-platform.yaml up` + +This will download the docker images for Kafka and Egeria, then create and start the two containers. Both kafka and Egeria will then automatically configure themselves. For Egeria, this means not only starting up the initial set of servers, but then loading the **CoreContentPack.omarchive** into the metadata repository, and then configuring all the servers. This can take several minutes the first time the containers are created. Subsequent startups will be much faster. + +4. Using either the **docker desktop** application or the docker command line you can see the two new containers running. To do this with the docker command line, you can issue: + +`docker ps` + +5. The environment is ready to be used. + +6. You can control the containers with docker compose commands - see [docker compose](https://docs.docker.com/reference/cli/docker/compose/). These commands can be used to administer and use the docker containers. + +## Next Steps + +Now that your Egeria environment is running and configured it is waiting for you to make requests. +Some tutorials for working with Egeria can be found at [Tutorials](https://egeria-project.org/education/tutorials/). For those that want to try the new python client, you can find a quick introduction at [pyegeria](https://getting-started-with-egeria.pdr-associates.com/recipe-6-charming-python.html). + +As always, your feedback and participation are welcome. + + +License: CC BY 4.0, Copyright Contributors to the ODPi Egeria project. \ No newline at end of file diff --git a/egeria-platform-compose/egeria-platform.yaml b/egeria-platform-compose/egeria-platform.yaml new file mode 100644 index 0000000..d959319 --- /dev/null +++ b/egeria-platform-compose/egeria-platform.yaml @@ -0,0 +1,58 @@ +--- +# SPDX-License-Identifier: Apache-2.0 +# Copyright Contributors to the Egeria project + + +# To run +# * Ensure Docker is installed and running +# * Start Egeria Platform stack using +# 'docker-compose -f ./egeria-platform.yaml up' +# +# Assumptions: +# * Ports 9443 and 9092 are available on the host system - these are the default ports for Kafka and Egeria. +# * by default, no external volumes are used - comments below give examples of using external volumes +# * by default, Egeria will start with its set of default servers (active-metadata-store,view-server, +# integration-daemon,engine-host,simple-metadata-store) - you can change this. +# * by default, strict SSL validation is turned off +# +# See the following link for more info on volumes & why we need to use root: +# https://docs.bitnami.com/containers/how-to/work-with-non-root-containers + + +services: + kafka: + image: 'bitnami/kafka:latest' + ports: + - '9092:9092' + environment: + - KAFKA_CFG_NODE_ID=0 + - KAFKA_CFG_AUTO_CREATE_TOPICS_ENABLE=true + - KAFKA_CFG_PROCESS_ROLES=controller,broker + - KAFKA_CFG_LISTENERS=PLAINTEXT://:9092,CONTROLLER://:9093 + - KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP=CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT + - KAFKA_CFG_CONTROLLER_QUORUM_VOTERS=0@kafka:9093 + - KAFKA_CFG_CONTROLLER_LISTENER_NAMES=CONTROLLER + + egeria-platform: + depends_on: + - kafka + image: 'docker.io/odpi/egeria-platform:latest' + ports: + - '9443:9443' + + + # Persistence +# volumes: +# - "YOUR DATA DIRECTORY"/data:/deployments/data +# - "YOUR EXTRA LIBRARY DIRECTORY"/extra:/deployments/extra +# - "ANOTHER DIRECTOR TO MOUNT"/:/deployments/user_mount + + +# +# Change external to true and create volumes manually if you wish to persist between runs +# +volumes: + zookeeper-data: + external: false + kafka-data: + external: false diff --git a/egeria-platform-jupyter-compose/.env b/egeria-platform-jupyter-compose/.env new file mode 100644 index 0000000..b87c2a9 --- /dev/null +++ b/egeria-platform-jupyter-compose/.env @@ -0,0 +1 @@ + CONFIG_JSON=$(cat config.json) \ No newline at end of file diff --git a/egeria-platform-jupyter-compose/Dockerfile-jupyter b/egeria-platform-jupyter-compose/Dockerfile-jupyter new file mode 100644 index 0000000..3472451 --- /dev/null +++ b/egeria-platform-jupyter-compose/Dockerfile-jupyter @@ -0,0 +1,42 @@ +# SPDX-License-Identifier: Apache-2.0 +# Copyright Contributors to the Egeria project. + +# This build script corrects some permission issues needed to run +# on some enterprise k8s environments. see https://github.com/odpi/egeria-jupyter-notebooks/issues/9 + +# The published image tag is taken from the numerical version of +# our base image, and appended with the contents of .tag-append (file) +FROM quay.io/jupyter/scipy-notebook + +RUN pip install --no-cache-dir 'pyegeria>=0.5.5.13' +RUN pip install --no-cache-dir 'rich' +RUN pip install --no-cache-dir 'unitycatalog' +RUN pip install --no-cache-dir 'pipx' +RUN pipx ensurepath +RUN pipx install pyegeria +RUN pipx ensurepath + +RUN alias view_integ='view_integ_daemon_status --integ_url https://host.docker.internal:9443 --view_url https://host.docker.internal:9443' +RUN alias view_plat='view_platform_status --url https://host.docker.internal:9443' + +RUN mkdir -p /home/jovyan/workspaces +RUN chmod +xrw /home/jovyan/workspaces +copy ../Start-Here.md /home/jovyan +# && \ +# fix-permissions "${CONDA_DIR}" && \ +# fix-permissions "/home/${NB_USER}" \ + +#ENTRYPOINT ["jupyter","notebook"] +# +#CMD ["python3", "/config_coco_core.py"] +#USER root + +# Needed to dynamically add the selected user on startup - see link below +#RUN chmod g+w /etc/passwd +# +#RUN chown -R $NB_UID:$NB_GID $HOME +# +## https://cloud.redhat.com/blog/jupyter-on-openshift-part-6-running-as-an-assigned-user-id +#RUN chgrp -Rf root /home/$NB_USER && chmod -Rf g+w /home/$NB_USER && chgrp -Rf root /opt/conda && chmod -Rf g+w /opt/conda +# +#USER 1000 diff --git a/egeria-platform-jupyter-compose/README.md b/egeria-platform-jupyter-compose/README.md new file mode 100644 index 0000000..09299f4 --- /dev/null +++ b/egeria-platform-jupyter-compose/README.md @@ -0,0 +1,72 @@ + + + +# Open Metadata Labs - Using Docker Compose + +The open metadata labs contain an interactive environment that allow you to +experiment with different capabilities of Egeria. More information about the labs can be found at: +[Overview of the Labs](https://egeria-project.org/education/open-metadata-labs/overview/). +The labs are written using Python Jupyter notebooks that +run in a Jupyter Server. The interactive exercises in the notebooks call python functions +that communicate with Egeria. An Apache Kafka server is used by Egeria for communications. + +One way to easily deploy a running +Open Metadata Labs environment is by using the Docker Compose scripts contained in this directory. + +A docker compose script, coco-lab-setup.yaml uses docker compose to deploy, configure and run a complete working +environment that includes: + +* Three Egeria OMAG Server Platforms + * egeria-core (on port 7443) + * egeria-datalake (on port 7444) + * egeria-dev (on port 7445) +* Kafka (on port 9192) +* Jupyter Server that is used to run the lab exercises (on port 9888) + +Note that the port numbers are different from tho commonly used defaults to simplify +the coexistence of this environment in a host that may already be running Egeria, Kafka or Jupyter. +Configurations can be changed in the docker compose script `coco-lab-setup.yaml`. + +# Getting Started + +To get started, you need a computer with Docker installed and configured. Our experience is with running Docker on Mac and +Linux machines, Windows machines should also work (reach out if you run into issues). Docker can be installed from +[Docker](https://docker.com). Docker compose is installed automatically if you install Dockker Desktop. +Compatible alternatives to Docker Compose exist but have not yet been validated. + +This deployment has been testing on older machines as well as current ones. Given the number of +servers we are running, allocating at 10gb of memory for Docker is recommended. + +The startup procedure is as follows: + +1. Start docker or docker desktop +2. Download or clone the git repository containing the Egeria Open Metadata Labs + * Download - a zip file of the repository can be downloaded by pressing the green `Code` button on [](https://github.com/odpi/egeria-jupyter-notebooks) + * unzip the file in your directory of choice + * Clone - If you want to fork the repo first then change the URL to your fork. + * Ensure you have the git command line installed. + * Change to a directory you want to use as the parent of the files + * From a terminal window, issue: `git clone https://github.com/dwolfson/egeria-jupyter-notebooks.git` +3. In a terminal window, from the `egeria-jupyter-notebooks` folder, issue the commmand: + `docker compose -f coco-lab-docker-compose/coco-lab-setup.yaml up --build` + * This should produce a large amount of output as images are downloaded and started and configured. Expect the process to take a few minutes the first time and less on subsequent starts. + * You should only need the `--build` option the first time you run this command. Subsequent startup calls would just be: + `docker compose -f coco-lab-docker-compose/coco-lab-setup.yaml up` +4. Once everything is started and configured you can run the labs from the jupyter server by: + * opening a web browser to: + `http://localhost:9888/lab/tree/egeria-labs/read-me-first.ipynb` + * Type in `egeria` into the box for token or password. This will launch Jupyter in the browser. + * The `read-me-first` jupyter notebook should now be displayed. You are ready to start! + + +5. Questions, observations and other feedback are always welcome - we welcome your participation in our community: + * https://egeria-project.org/guides/community/ + + + + + + +---- +License: [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/), +Copyright Contributors to the ODPi Egeria project. diff --git a/egeria-platform-jupyter-compose/Start-Here.md b/egeria-platform-jupyter-compose/Start-Here.md new file mode 100644 index 0000000..4b85d22 --- /dev/null +++ b/egeria-platform-jupyter-compose/Start-Here.md @@ -0,0 +1,16 @@ + + +# Using the Egeria-Platform-Jupyter-Compose Environment + +If you are viewing this file in a Jupyter server then now you are ready to explore and use this basic Egeria environment. + +If you are running Jupyter, on the left hand navigation panel you should see two folders: +* work - for your own notebooks. +* workbooks - contains some starter jupyter notebooks and demos. + +These folders reside outside of the container and by default located in the `egeria-workspaces` directory that contains the scripts used to start up this environment. These locations can be changed by altering the **Docker Compose** script +`egeria-platform-jupyter-compose.yaml` using a text editor. + + + +For more information, please see [Egeria Docker Compose](https://egeria-project.org/education/open-metadata-labs/overview) \ No newline at end of file diff --git a/egeria-platform-jupyter-compose/application.properties b/egeria-platform-jupyter-compose/application.properties new file mode 100755 index 0000000..fff0e0f --- /dev/null +++ b/egeria-platform-jupyter-compose/application.properties @@ -0,0 +1,226 @@ +# SPDX-License-Identifier: Apache-2.0 +# Copyright Contributors to the ODPi Egeria project. + +# ======================================================================================================== +# The application.properties file is used to configure the OMAG Server Platform which is implemented as +# a spring boot application. + +############################################### +### Default port for the OMAG Server Platform +################################################ +server.port=9443 + +############################################### +### Set up the configuration document store the OMAG Server Platform +### The values below will set up the clear text config document store +### (the default is the encrypted config document store). +################################################ +#platform.configstore.provider=org.odpi.openmetadata.adapters.adminservices.configurationstore.file.FileBasedServerConfigStoreProvider +#platform.configstore.endpoint=data/servers/{0}/config/{0}.config + +############################################### +### Set up the platform metadata security connector that provides authorization +### for platform administration, server operations and diagnostic calls. +### By default, there is no platform metadata security connector. +### The values below are for a sample platform metadata security connector where the only userId +### that is permitted to use the administration and platform services is `garygeeke`. +############################################### +#platform.security.provider=org.odpi.openmetadata.metadatasecurity.samples.CocoPharmaPlatformSecurityProvider +#platform.security.name=Coco Pharmaceuticals Platform + +############################################### +### Set up the default configuration document for any new OMAG Server configurations. +############################################### +#platform.default.config.document=\ +# {\ +# "class": "OMAGServerConfig",\ +# "organizationName": "myOrg",\ +# "maxPageSize": 1200,\ +# "eventBusConfig": \ +# {\ +# "class": "EventBusConfig",\ +# "topicURLRoot": "egeria.omag",\ +# "configurationProperties":\ +# {\ +# "producer": {"bootstrap.servers": "{{kafkaEndpoint}}"},\ +# "consumer": {"bootstrap.servers": "{{kafkaEndpoint}}"}\ +# }\ +# }\ +# } + +################################################ +### Placeholder variables are added to field values in the configuration document +### when the server is being configured using double curly braces. They are replaced by +### the values specified in platform.placeholder.variables each time the server starts up. +### +### The "kafkaEndpoint" value is the Apache Kafka endpoint, and it is used in the active-metadata-store +### sample configuration. If your Apache Kafka broker is listening on a different endpoint +### and you want to use active-metadata-store, change this variable to your Apache Kafka's address. +################################################ +platform.placeholder.variables=\ + {\ + "kafkaEndpoint" : "localhost:9092",\ + "egeriaEndpoint" : "https://localhost:9443"\ + } + +################################################ +### startup servers configuration +################################################ +#userId used to start up the list of configured servers default is 'system' +startup.user=system +# Comma separated names of servers to be started. The server names should be unquoted. +#git startup.server.list=active-metadata-store,engine-host,integration-daemon,view-server,simple-metadata-store + +################################################ +### SSL security. +# The keystore determines the information sent out by the server to identify itself. +# The truststore is where the certificates of trusted servers the platform is calling are located. +# (Note SSL certificate checking is performed on client-side only.) +################################################ +server.ssl.key-store=keystore.p12 +server.ssl.key-store-password=egeria +server.ssl.keyStoreType=PKCS12 +server.ssl.keyAlias=egeriaserverchassis + +server.ssl.trust-store=truststore.p12 +server.ssl.trust-store-password=egeria + +# WARNING! setting 'strict.ssl=false' allows java clients to open https connections without checking the validity of +# certificates from the servers it is calling. +# Alternate you can import self-signed certificates into java truststore or set up a truststore only for this app +# by adding the store into server.ssl.trust-store parameter +strict.ssl=true + + +################################################ +# User security +################################################ + +# Authentication source (possible values: demo, ldap, ad) +authentication.source=demo +# Authentication mode (possible values: session,token,redis) +authentication.mode=token + +#token timeout in minutes +token.timeout=15 +token.absolute.timeout=720 +token.secret=doNotTell + +#LDAP authentication + +ldap.domain= +ldap.user.search.base=ou=people,dc=egeria,dc=com +ldap.user.search.filter=uid={0} +ldap.group.search.base=ou=Groups,dc=egeria,dc=com +ldap.group.search.filter=member={0} +ldap.url=ldap://localhost:389 +ldap.group.role.attribute= +ldap.npa.dn= +ldap.npa.password= +#ldap.user.dn.patterns patterns is a list of values separated by ";" as comma is used in the ldap pattern +ldap.user.dn.patterns= + +# Redis configuration +#redis.host=localhost +#redis.port=6379 + +################################################ +### Additional demo users configuration for when authentication.source=demo +### This file is located in the resources folder of the user-authn module and built into its runtime jar +################################################ +spring.config.import=classpath:demo-users.yml + +################################################ +### Comma separated list of header names to extract from incoming HTTP requests and add to thread local. +### The default value is null and adds no headers. +### Setting the list to * means all headers are captured. +### Otherwise, list the header name in use. +################################################ +authn.header.name.list= + +################################################ +### CORS +################################################ +# Comma-separated list of origins. +# Example configuration below is for setting up local development environment where egeria-ui is hosted on one of the two urls. +# cors.allowed-origins=http://localhost,http://localhost:8081 +cors.allowed-origins=* + +################################################ +# landing page (/api/public/app/info) +################################################ +app.description=Have a question? || Get in touch via our Slack community https://slack.lfai.foundation/ @@What is Open Metadata? || Find out more on our website https://egeria-project.org/ @@Have more cool ideas? || Feel free to let us know your ideas so we can make it better. +app.title=Egeria Open Metadata | Find the right data with governance + +# ############################################################## +# Component visibility for Role based access ################### +# ############################################################## +# How it works? +# +# The roles are defined in external authentication source (provider) configured with `authentication.source`. +# For demo purposes, we are providing simple file based authentication provider. See demo-users.yml +# The matrix controlling what components are allowed on the UI views for specific role is defined in the 'role.visibleComponents' prefixed properties as follows" +# +# role.visibleComponents.{ROLE-1}={component-name-1} +# role.visibleComponents.{ROLE-2}={component-name-1},{component-name-2} +# +# This will configure the application to show the component named 'component-name-1' for all uses assigned to 'ROLE-1' +# In the same way, users that have assigned 'ROLE-2' can see more 'component-name-1' and 'component-name-2'. +# It is also possible to use wildcard '*' to enable full visibility of all components to users in the given role. +# +# Complete list of components names that can be used: +# +# about +# asset-catalog +# asset-details +# asset-details-print +# glossary +# repository-explorer +# type-explorer +# asset-lineage +# asset-lineage-print +# end-to-end +# ultimate-source +# ultimate-destination +# vertical-lineage +# +# Below is the default configuration for the two COCO_PHARMA roles we use for demo: + +role.visibleComponents.COCO_PHARMA_USER=about,asset-catalog,asset-details,asset-details-print,asset-lineage,asset-lineage-print,end-to-end,ultimate-source,ultimate-destination,vertical-lineage,glossary,repository-explorer +role.visibleComponents.COCO_PHARMA_ADMIN=* + +################################################ +### Which java packages should be scanned to locate the Spring resource definitions that define the REST APIs? +################################################ +scan.packages=org.odpi.openmetadata.* + +################################################ +### Logging +################################################ +logging.level.root=OFF +logging.level.org.springframework=ERROR +logging.level.org.springframework.boot.web.embedded.tomcat=INFO +logging.level.org.odpi.openmetadata.platformchassis.springboot=INFO +#tracing REST calls +#logging.level.org.odpi.openmetadata.commonservices.ffdc.RESTCallLogger=DEBUG + +################################################ +### Swagger Docs +################################################ +springdoc.version='@springdoc.version@' +springdoc.api-docs.enabled=true +springdoc.api-docs.path=/v3/api-docs +springdoc.swagger-ui.path=/swagger-ui.html +springdoc.swagger-ui.displayRequestDuration=true +springdoc.swagger-ui.tagsSorter=alpha +springdoc.swagger-ui.operationsSorter=alpha +springdoc.swagger-ui.docExpansion=none + +################################################ +### Spring Boot Actuator +################################################ +# Endpoints web configuration +#management.endpoints.web.exposure.include=* +management.health.cassandra.enabled=false +management.health.redis.enabled=false +management.health.ldap.enabled=false \ No newline at end of file diff --git a/egeria-platform-jupyter-compose/before-notebook.d/config-coco.sh b/egeria-platform-jupyter-compose/before-notebook.d/config-coco.sh new file mode 100755 index 0000000..75423c8 --- /dev/null +++ b/egeria-platform-jupyter-compose/before-notebook.d/config-coco.sh @@ -0,0 +1,18 @@ +#!/bin/bash +# +# SPDX-License-Identifier: Apache-2.0 +# Copyright Contributors to the ODPi Egeria project. +# +# Coco Pharmaceuticals Lab Docker Compose configuration +# +# This shell script is automatically invoked when the Jupyter Container is initiated as part of the Docker Compose +# configuration.The script configures and activates the Egeria OMAG servers on each of the three Egeria OMAG Server +# Platforms that are part of the sample Coco Pharmaceuticals deployment environment. + +#export PYTHONDONTWRITEBYTECODE=1 +#python3 /home/jovyan/egeria-labs/common/config_coco_core.py +#python3 /home/jovyan/egeria-labs/common/config_coco_datalake.py +#python3 /home/jovyan/egeria-labs/common/config_coco_development.py +#echo "Launching Jupyter notebook server.." + + diff --git a/egeria-platform-jupyter-compose/egeria-platform-jupyter-compose.yaml b/egeria-platform-jupyter-compose/egeria-platform-jupyter-compose.yaml new file mode 100644 index 0000000..36ed8bd --- /dev/null +++ b/egeria-platform-jupyter-compose/egeria-platform-jupyter-compose.yaml @@ -0,0 +1,95 @@ +--- +# SPDX-License-Identifier: Apache-2.0 +# Copyright Contributors to the Egeria project + + +# To run +# * Ensure Docker is installed and running +# * Start Egeria Platform stack from the 'coco-lab-docker-compose' folder using: +# 'docker compose -f ./egeria-platform-jupyter-uc-compose.yaml up --build' +# * Subsequent startups can be performed without the '--build' at the end +# * Uninstall the containers by issuing 'docker compose -f ./egeria-platform-jupyter-uc-compose.yaml down' +# +# Assumptions: +# * Ports 7443, 7444, 7445 and 9192 are available on the host system - these are the default ports for Kafka and Egeria. +# * by default, the jupyter notebooks in the 'coco-jupyter-labs' folder are mounted and available for use by the jupyter noteboos +# * by default, strict SSL validation is turned off +# +# + +services: + jupyter-hub: + depends_on: + egeria-main: + condition: service_healthy + + image: quay.io/jupyter/scipy-notebook + container_name: jupyter-work + ports: + - 8888:8888 + environment: + JUPYTER_ENABLE_LAB: "yes" + JUPYTER_TOKEN: "egeria" + build: + dockerfile: Dockerfile-jupyter + + volumes: + - ./before-notebook.d:/usr/local/bin/before-notebook.d + - ../work:/home/jovyan/work + - ../workspaces:/home/jovyan/workspaces + + + kafka: + image: 'bitnami/kafka:latest' + ports: + - '9092:9092' + + environment: + - KAFKA_CFG_NODE_ID=0 + - KAFKA_CFG_AUTO_CREATE_TOPICS_ENABLE=true + - KAFKA_CFG_PROCESS_ROLES=controller,broker + - KAFKA_CFG_LISTENERS=PLAINTEXT://:9092,CONTROLLER://:9093 + - KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP=CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT + - KAFKA_CFG_CONTROLLER_QUORUM_VOTERS=0@kafka:9093 + - KAFKA_CFG_CONTROLLER_LISTENER_NAMES=CONTROLLER + + + + egeria-main: + depends_on: + - kafka + image: 'egeria-platform:uc1' +# image: 'docker.io/odpi/egeria-platform:stable' + ports: + - '9443:9443' + environment: + - XTDB_ENABLE_BYTEUTILS_SHA1=True + - XTDB_DISABLE_LIBCRYPTO=True + - startup.server.list=active-metadata-store,engine-host,integration-daemon,view-server,simple-metadata-store + - server.port=9443 + + healthcheck: + test: curl -k -X GET "https://localhost:9443/open-metadata/platform-services/users/garygeeke/server-platform/origin" || exit 1 + interval: 20s + timeout: 10s + retries: 3 + start_period: 10s + start_interval: 5s + + volumes: + - ../landing_area:/deployments/landing-area + - ../logs:/deployments/logs + # - "YOUR EXTRA LIBRARY DIRECTORY"/extra:/deployments/extra + # - "ANOTHER DIRECTOR TO MOUNT"/:/deployments/user_mount + + +# +# Change external to true and create volumes manually if you wish to persist between runs +# +volumes: + zookeeper-data: + external: false + kafka-data: + external: false + db_home: + external: false diff --git a/egeria-platform-postgres-compose/README.md b/egeria-platform-postgres-compose/README.md new file mode 100644 index 0000000..4750b4f --- /dev/null +++ b/egeria-platform-postgres-compose/README.md @@ -0,0 +1,83 @@ +# Overview +This directory contains sample Docker Compose scripts to support the deployment of Egeria for experimentation, +development, and learning. Rather than having to install Egeria, prerequisites and tools separately, these scripts make +it easy to get a stack running quickly. +These are not meant for production use. Please see the [Planning Guide](https://egeria-project.org/guides/planning/) +for more information about designing Egeria deployments. The Egeria community has also created samples for other +deployment styles, such as Cloud Native approaches and the use of Helm charts to configure Kubernetes clusters. These +options may be better starting points for production deployments - depending upon your requirements. +Please feel free to engage with the community on our slack channel - we'd love your feedback and participation. + + +For a quick and simple environment to explore some of Egeria's base capabilities, the **egeria-platform.yml** Docker Compose +deployment may be a good starting point. Once this script executes successfully, you will have two docker containers running. One for the Egeria platform and one for Kafka. With this running configuration, you can work with any of Egeria's standard interfaces - java APIs, python APIs, or just plain RESTful http calls - and of course, to make use of tools and interfaces that have been built using these APIs. + +The set of **Docker Compose** configurations will grow and evolve over time to cover additional scenarios. For example, +the folder `egeria-platform-postgres-compose` contains a docker compose configuration that adds a Postgres +database along with the Egeria OMAG platform and Kafka servers. This sets the stage emerging scenarios that +utilize a relational database to collect Egeria derived information such as Audit logs for additional analysis and dashboarding. +Please see the embedded README.md files for more details. + +# Contents + +* egeria-platform-compose - a basic Egeria OMAG platform standalone deployment that includes Kafka as well as Egeria. +* egeria-platform-postgres-compose - deploys and configures a Postgres relational database as well as Kafka and Egeria. + +Our first docker compose script is called **egeria-platform.yml**. After running this script, you will have a running environment +that consists of a single Egeria runtime platform and the Apache Kafka event system. Information about configuring +Egeria can be found at [Configuring Egeria](https://egeria-project.org/guides/admin/configuring-the-omag-server-platform/). +We use standard, out-of-the-box configurations for both - a minimal amount of configuration for: + +## Egeria Platform - Default Configuration +We use the Egeria platform docker image - [egeria-platform](https://hub.docker.com/r/odpi/egeria-platform). + +* Port - By default the platform uses port 9443 and exposes this port to the host environment, This means that Egeria requests +can be made to platform URL **https://localhost:9443** or, if your environment is configured to support it, it can use +the domain name of your host machine. +* SSL - By default strict SSL is set to false +* Auto-Started Servers - by default a useful set of Egeria Operational Metadata and Governance (OMAG) servers are pre-installed +and started when the Egeria platform is started. The pre-configured and started servers are: + * simple-metadata-store + * active-metadata-store + * engine-host + * integration-daemon + * view-server +A description of these servers can be found at [sample configs](open-metadata-resources/open-metadata-deployment/sample-configs/README.md) +* Content Packs - pre-constructed information sets that can be used to configure Egeria and pre-load metadata, reference data and glossary data. See [Content Packs](https://egeria-project.org/content-packs/). +* Out-of-the-box Connectors - descriptions of the integration connectors can be found at [Integration Connectors](https://egeria-project.org/connectors/). + +## Kafka - configured for Egeria +We use the bitnami/kafka image described at [kafka](https://hub.docker.com/r/bitnami/kafka) +* Port - We use the default port of 9092 for Kafka. This port is also exposed in the host environment. Changing this port also requires corresponding changes to the Egeria configuration. +* Other configuration can be seen in the *egeria-platform.yaml* file. + +# Usage +Follow these steps to use Docker Compose. + +1. Install and Configure Docker and Docker Compose. + * Docker and Docker compose must be installed and running - see https://docs.docker.com/install/ + * Configure docker with at least 6GB memory +2. Download the [**egeria-platform.yaml**](https://raw.githubusercontent.com/odpi/egeria/main/open-metadata-resources/open-metadata-deployment/docker-compose/egeria-platform-compose/egeria-platform.yaml) +3. Run the docker compose script from a terminal window in the directory where you downloaded `egeria-platform.yaml`. At the command line issue: + + `docker compose -f egeria-platform.yaml up` + +This will download the docker images for Kafka and Egeria, then create and start the two containers. Both kafka and Egeria will then automatically configure themselves. For Egeria, this means not only starting up the initial set of servers, but then loading the **CoreContentPack.omarchive** into the metadata repository, and then configuring all the servers. This can take several minutes the first time the containers are created. Subsequent startups will be much faster. + +4. Using either the **docker desktop** application or the docker command line you can see the two new containers running. To do this with the docker command line, you can issue: + +`docker ps` + +5. The environment is ready to be used. + +6. You can control the containers with docker compose commands - see [docker compose](https://docs.docker.com/reference/cli/docker/compose/). These commands can be used to administer and use the docker containers. + +## Next Steps + +Now that your Egeria environment is running and configured it is waiting for you to make requests. +Some tutorials for working with Egeria can be found at [Tutorials](https://egeria-project.org/education/tutorials/). For those that want to try the new python client, you can find a quick introduction at [pyegeria](https://getting-started-with-egeria.pdr-associates.com/recipe-6-charming-python.html). + +As always, your feedback and participation are welcome. + + +License: CC BY 4.0, Copyright Contributors to the ODPi Egeria project. \ No newline at end of file diff --git a/egeria-platform-postgres-compose/docker-entrypoint-initdb.d/init_egeria_observations.sql b/egeria-platform-postgres-compose/docker-entrypoint-initdb.d/init_egeria_observations.sql new file mode 100644 index 0000000..9e01408 --- /dev/null +++ b/egeria-platform-postgres-compose/docker-entrypoint-initdb.d/init_egeria_observations.sql @@ -0,0 +1,126 @@ +create user egeria_admin with superuser login password 'admin4egeria'; +create user egeria_user with login password 'user4egeria'; +create database egeria_observations; + +grant all privileges on database egeria_observations to egeria_admin, egeria_user; + + +\c egeria_observations; +create schema open_metadata; +create schema audit_log; +grant all on schema open_metadata, audit_log to egeria_admin, egeria_user; + +DROP TABLE IF EXISTS audit_log.al_api_calls; +CREATE TABLE audit_log.al_api_calls (thread_id BIGINT NOT NULL, server_name TEXT NOT NULL, user_name TEXT NOT NULL, operation_name TEXT NOT NULL, service_name TEXT NOT NULL, call_time TIMESTAMP(6) WITHOUT TIME ZONE NOT NULL); +COMMENT ON TABLE audit_log.al_api_calls IS 'Calls to Egeria REST APIs'; +COMMENT ON COLUMN audit_log.al_api_calls.thread_id IS 'Thread running the request'; +COMMENT ON COLUMN audit_log.al_api_calls.server_name IS 'Name of the called server'; +COMMENT ON COLUMN audit_log.al_api_calls.user_name IS 'Identifier of calling user'; +COMMENT ON COLUMN audit_log.al_api_calls.operation_name IS 'Name of the called method'; +COMMENT ON COLUMN audit_log.al_api_calls.service_name IS 'The service supporting the called method'; +COMMENT ON COLUMN audit_log.al_api_calls.call_time IS 'Time that the request was made.'; +DROP TABLE IF EXISTS audit_log.al_asset_activity; +CREATE TABLE audit_log.al_asset_activity (thread_id BIGINT NOT NULL, server_name TEXT NOT NULL, call_time TIMESTAMP(6) WITHOUT TIME ZONE NOT NULL, asset_operation TEXT NOT NULL, asset_guid TEXT NOT NULL, asset_type TEXT NOT NULL, operation_name TEXT NOT NULL, service_name TEXT NOT NULL, user_name TEXT NOT NULL); +COMMENT ON TABLE audit_log.al_asset_activity IS 'User activity around an om_asset'; +COMMENT ON COLUMN audit_log.al_asset_activity.thread_id IS 'Thread where the request ran'; +COMMENT ON COLUMN audit_log.al_asset_activity.server_name IS 'Name of the called server'; +COMMENT ON COLUMN audit_log.al_asset_activity.call_time IS 'Time thatthe request was made'; +COMMENT ON COLUMN audit_log.al_asset_activity.asset_operation IS 'Create, Update, Delete, Attachment, Feedback'; +COMMENT ON COLUMN audit_log.al_asset_activity.asset_guid IS 'Unique identifier of the om_asset'; +COMMENT ON COLUMN audit_log.al_asset_activity.asset_type IS 'Type of the om_asset'; +COMMENT ON COLUMN audit_log.al_asset_activity.operation_name IS 'Called method'; +COMMENT ON COLUMN audit_log.al_asset_activity.service_name IS 'Name of the called service'; +COMMENT ON COLUMN audit_log.al_asset_activity.user_name IS 'Name of the requesting user.'; +DROP TABLE IF EXISTS audit_log.al_audit_events; +CREATE TABLE audit_log.al_audit_events (message_ts TIMESTAMP(6) WITHOUT TIME ZONE NOT NULL, server_name TEXT, action_description TEXT, severity_code TEXT, severity TEXT, message_id TEXT, message_text TEXT, message_parameters TEXT, system_action TEXT, user_action TEXT, exception_class_name TEXT, exception_message TEXT, exception_stacktrace TEXT, organization TEXT, component_name TEXT, additional_info TEXT, log_record_id TEXT NOT NULL, thread_id BIGINT, CONSTRAINT audit_events_pk PRIMARY KEY (log_record_id, message_ts)); +COMMENT ON COLUMN audit_log.al_audit_events.thread_id IS 'Thread where the request ran'; +DROP TABLE IF EXISTS audit_log.al_egeria_components; +CREATE TABLE audit_log.al_egeria_components (component_id INTEGER NOT NULL, development_status CHARACTER VARYING(20), component_name TEXT, component_description TEXT, component_wiki_url TEXT, CONSTRAINT egeriacomponents_ix1 UNIQUE (component_id)); +DROP TABLE IF EXISTS audit_log.al_egeria_exceptions; +CREATE TABLE audit_log.al_egeria_exceptions (exception_class_name TEXT NOT NULL, exception_message TEXT NOT NULL, system_action TEXT NOT NULL, user_action TEXT NOT NULL, message_ts TEXT NOT NULL, log_record_id TEXT NOT NULL, CONSTRAINT egeria_exceptions_pk PRIMARY KEY (log_record_id)); +COMMENT ON COLUMN audit_log.al_egeria_exceptions.message_ts IS 'Timestamp of log record'; +COMMENT ON COLUMN audit_log.al_egeria_exceptions.log_record_id IS 'Unique identifier of the reporting log record.'; +DROP TABLE IF EXISTS audit_log.al_omag_servers; +CREATE TABLE audit_log.al_omag_servers (server_name TEXT NOT NULL, server_type TEXT, organization TEXT, metadata_collection_id TEXT, CONSTRAINT omag_servers_pk PRIMARY KEY (server_name)); +COMMENT ON COLUMN audit_log.al_omag_servers.server_name IS 'Name of the server'; +COMMENT ON COLUMN audit_log.al_omag_servers.server_type IS 'Type of server'; +COMMENT ON COLUMN audit_log.al_omag_servers.organization IS 'Name ofthe organization that runs this server.'; +COMMENT ON COLUMN audit_log.al_omag_servers.metadata_collection_id IS 'Identifier for the metadata collection beinf maintained by this server'; + +DROP TABLE IF EXISTS open_metadata.om_asset; +CREATE TABLE open_metadata.om_asset (resource_name TEXT, resource_description TEXT, version_id TEXT, display_name TEXT, display_description TEXT, asset_guid TEXT NOT NULL, qualified_name TEXT NOT NULL, display_summary TEXT, abbrev TEXT, usage TEXT, additional_properties TEXT, owner_guid TEXT, owner_type TEXT, origin_org_guid TEXT, origin_biz_cap_guid TEXT, zone_names TEXT, asset_type TEXT NOT NULL, resource_loc_guid TEXT, confidentiality INTEGER, confidence INTEGER, criticality INTEGER, metadata_collection_id TEXT NOT NULL, license_guid TEXT, sync_time TIMESTAMP(6) WITHOUT TIME ZONE NOT NULL, last_update_timestamp TIMESTAMP(6) WITHOUT TIME ZONE, last_updated_by TEXT, creation_timestamp TIMESTAMP(6) WITHOUT TIME ZONE NOT NULL, created_by TEXT, maintained_by TEXT, archived TIMESTAMP(6) WITHOUT TIME ZONE, tags TEXT, semantic_term TEXT, PRIMARY KEY (asset_guid, sync_time)); +COMMENT ON TABLE open_metadata.om_asset IS 'Assets catalogued in the Egeria ecosystem'; +COMMENT ON COLUMN open_metadata.om_asset.sync_time IS 'The time at which egeria update this row.'; +DROP TABLE IF EXISTS open_metadata.om_asset_types; +CREATE TABLE open_metadata.om_asset_types (leaf_type TEXT NOT NULL, type_description TEXT, super_types JSONB, CONSTRAINT asset_types_pk PRIMARY KEY (leaf_type)); +DROP TABLE IF EXISTS open_metadata.om_certification_type; +CREATE TABLE open_metadata.om_certification_type (certification_type_guid TEXT NOT NULL, certification_title TEXT NOT NULL, certification_summary TEXT, PRIMARY KEY (certification_type_guid)); +COMMENT ON TABLE open_metadata.om_certification_type IS 'map certification guids to names'; +DROP TABLE IF EXISTS open_metadata.om_certifications; +CREATE TABLE open_metadata.om_certifications (referenceable_guid TEXT NOT NULL, certification_guid TEXT NOT NULL, certification_type_guid TEXT NOT NULL, start_date DATE, end_date DATE, sync_time TIMESTAMP(6) WITHOUT TIME ZONE NOT NULL, PRIMARY KEY (certification_guid, sync_time)); +COMMENT ON TABLE open_metadata.om_certifications IS 'om_certifications associated wtih assets'; +DROP TABLE IF EXISTS open_metadata.om_collaboration_activity; +CREATE TABLE open_metadata.om_collaboration_activity (element_guid TEXT NOT NULL, sync_time TIMESTAMP(6) WITHOUT TIME ZONE NOT NULL, num_comments INTEGER, num_ratings INTEGER, avg_rating INTEGER, num_tags INTEGER, num_likes INTEGER, PRIMARY KEY (element_guid, sync_time)); +COMMENT ON TABLE open_metadata.om_collaboration_activity IS 'Track the user feedback over time'; +COMMENT ON COLUMN open_metadata.om_collaboration_activity.element_guid IS 'Either an om_asset or glossary element'; +COMMENT ON COLUMN open_metadata.om_collaboration_activity.sync_time IS 'The last time that the information was updated in the database by Egeria.'; +DROP TABLE IF EXISTS open_metadata.om_context_event_types; +CREATE TABLE open_metadata.om_context_event_types (guid TEXT NOT NULL, qualified_name TEXT NOT NULL, ce_type_name TEXT NOT NULL, description TEXT); +DROP TABLE IF EXISTS open_metadata.om_context_events; +CREATE TABLE open_metadata.om_context_events (guid TEXT NOT NULL, qualified_name TEXT NOT NULL, display_name TEXT NOT NULL, description TEXT, event_effect TEXT, context_event_type TEXT NOT NULL, planned_start_date DATE, planned_duration NUMERIC, actual_duration NUMERIC, repeat_interval NUMERIC, planned_completion_date DATE, actual_completion_date DATE, reference_effective_from DATE, reference_effective_to DATE, additional_properties TEXT); +DROP TABLE IF EXISTS open_metadata.om_contributions; +CREATE TABLE open_metadata.om_contributions (user_guid TEXT NOT NULL, snapshot_timestamp TIMESTAMP(6) WITHOUT TIME ZONE NOT NULL, karma_points BIGINT, PRIMARY KEY (user_guid, snapshot_timestamp)); +COMMENT ON TABLE open_metadata.om_contributions IS 'This reflects the om_contributions per user over time.'; +DROP TABLE IF EXISTS open_metadata.om_correlation_properties; +CREATE TABLE open_metadata.om_correlation_properties (external_identifier TEXT NOT NULL, last_updated_by TEXT, last_update_time TIMESTAMP(6) WITHOUT TIME ZONE, created_by TEXT, version BIGINT, creation_time TIMESTAMP(6) WITHOUT TIME ZONE, type_name TEXT, egeria_owned BOOLEAN NOT NULL, additional_properties TEXT, element_guid TEXT NOT NULL, external_source_guid TEXT NOT NULL, last_confirmed_sync_time TIMESTAMP(6) WITHOUT TIME ZONE NOT NULL, PRIMARY KEY (external_identifier, element_guid, external_source_guid, last_confirmed_sync_time)); +COMMENT ON TABLE open_metadata.om_correlation_properties IS 'most of the information comes from external_id entity that represents an instance in a 3rd party catalog. This includes the user information from that third party.'; +DROP TABLE IF EXISTS open_metadata.om_data_fields; +CREATE TABLE open_metadata.om_data_fields (data_field_guid TEXT NOT NULL, data_field_name TEXT, version_id CHARACTER VARYING(80), semantic_term CHARACTER VARYING(80), has_profile BOOLEAN, confidentiality_level INTEGER, asset_qualified_name TEXT, asset_guid TEXT NOT NULL, sync_time TIMESTAMP(6) WITHOUT TIME ZONE NOT NULL, PRIMARY KEY (data_field_guid, sync_time)); +DROP TABLE IF EXISTS open_metadata.om_department; +CREATE TABLE open_metadata.om_department (dep_id CHARACTER VARYING(40) NOT NULL, dep_name TEXT, manager CHARACTER VARYING(40), parent_department TEXT, sync_time TIMESTAMP(6) WITHOUT TIME ZONE NOT NULL, PRIMARY KEY (dep_id, sync_time)); +COMMENT ON TABLE open_metadata.om_department IS 'Maps Department codes to Department names'; +DROP TABLE IF EXISTS open_metadata.om_external_audit_logs; +CREATE TABLE open_metadata.om_external_audit_logs (metadata_collection_id TEXT NOT NULL, external_identifier TEXT NOT NULL, activity_timestamp TIMESTAMP(6) WITHOUT TIME ZONE NOT NULL, activity_type TEXT, user_id TEXT, event_key TEXT, PRIMARY KEY (metadata_collection_id, external_identifier, activity_timestamp)); +DROP TABLE IF EXISTS open_metadata.om_external_user; +CREATE TABLE open_metadata.om_external_user (metadata_collection_id TEXT NOT NULL, external_user TEXT NOT NULL, user_id_guid TEXT, start_time TIMESTAMP(6) WITHOUT TIME ZONE, end_time TIMESTAMP(6) WITHOUT TIME ZONE, PRIMARY KEY (metadata_collection_id, external_user)); +COMMENT ON TABLE open_metadata.om_external_user IS 'Capture the user information from external systems that may or may not have mapped identities with Egeria.'; +DROP TABLE IF EXISTS open_metadata.om_glossary; +CREATE TABLE open_metadata.om_glossary (glossary_name TEXT, glossary_language TEXT, classifications TEXT, glossary_description TEXT, glossary_guid TEXT NOT NULL, qualified_name TEXT NOT NULL, number_terms BIGINT, number_categories INTEGER, num_linked_terms BIGINT, usage TEXT, additional_properties TEXT, owner_guid TEXT, owner_type TEXT, metadata_collection_id TEXT, license_guid TEXT, last_update_timestamp TIMESTAMP(6) WITHOUT TIME ZONE, creation_timestamp TIMESTAMP(6) WITHOUT TIME ZONE NOT NULL, sync_time TIMESTAMP(6) WITHOUT TIME ZONE NOT NULL, PRIMARY KEY (glossary_guid, sync_time)); +COMMENT ON TABLE open_metadata.om_glossary IS 'Glossaries catalogued in the Egeria ecosystem'; +COMMENT ON COLUMN open_metadata.om_glossary.last_update_timestamp IS 'This is the last update time from the glossary element.'; +DROP TABLE IF EXISTS open_metadata.om_license; +CREATE TABLE open_metadata.om_license (license_guid TEXT NOT NULL, license_name TEXT, license_description TEXT, PRIMARY KEY (license_guid)); +DROP TABLE IF EXISTS open_metadata.om_location; +CREATE TABLE open_metadata.om_location (location_guid TEXT NOT NULL, location_name TEXT, location_type TEXT, PRIMARY KEY (location_guid)); +DROP TABLE IF EXISTS open_metadata.om_metadata_collection; +CREATE TABLE open_metadata.om_metadata_collection (metadata_collection_id TEXT NOT NULL, metadata_collection_name TEXT, metadata_collection_type CHARACTER VARYING(40), deployed_impl_type TEXT, PRIMARY KEY (metadata_collection_id)); +COMMENT ON COLUMN open_metadata.om_metadata_collection.metadata_collection_type IS 'local cohort vs external source etc - instance provenance type'; +COMMENT ON COLUMN open_metadata.om_metadata_collection.deployed_impl_type IS 'This is the type of system (postgres vs db2 vs atlas)'; +DROP TABLE IF EXISTS open_metadata.om_reference_levels; +CREATE TABLE open_metadata.om_reference_levels (identifier INTEGER NOT NULL, classification_name TEXT NOT NULL, display_name TEXT, text TEXT, PRIMARY KEY (identifier, classification_name)); +COMMENT ON TABLE open_metadata.om_reference_levels IS 'A table to hold the different reference levels for confidentiality, confidence, criticality etc.'; +DROP TABLE IF EXISTS open_metadata.om_related_assets; +CREATE TABLE open_metadata.om_related_assets (end1_guid TEXT NOT NULL, end2_guid TEXT NOT NULL, end1_attribute_nm TEXT, end2_attribute_nm TEXT, rel_type_nm TEXT, sync_time TIMESTAMP(6) WITHOUT TIME ZONE NOT NULL, relationship_guid TEXT NOT NULL, PRIMARY KEY (relationship_guid, sync_time)); +DROP TABLE IF EXISTS open_metadata.om_role; +CREATE TABLE open_metadata.om_role (role_guid TEXT NOT NULL, role_name TEXT, role_type TEXT, headcount INTEGER, sync_time TIMESTAMP(6) WITHOUT TIME ZONE NOT NULL, PRIMARY KEY (role_guid, sync_time)); +DROP TABLE IF EXISTS open_metadata.om_role2user; +CREATE TABLE open_metadata.om_role2user (role_guid TEXT NOT NULL, user_guid TEXT NOT NULL, sync_time TIMESTAMP(6) WITHOUT TIME ZONE NOT NULL, start_date TIMESTAMP(6) WITHOUT TIME ZONE, end_date TIMESTAMP(6) WITHOUT TIME ZONE, relationship_guid TEXT NOT NULL, PRIMARY KEY (relationship_guid, sync_time)); +COMMENT ON TABLE open_metadata.om_role2user IS 'Mapping of roles to users'; +DROP TABLE IF EXISTS open_metadata.om_term_activity; +CREATE TABLE open_metadata.om_term_activity (term_name TEXT, term_guid TEXT NOT NULL, qualified_name TEXT NOT NULL, term_summary TEXT, version_id TEXT, owner_guid TEXT, owner_type TEXT, confidentiality INTEGER, confidence INTEGER, criticality INTEGER, last_feedback_timestamp TIMESTAMP(6) WITHOUT TIME ZONE, creation_timestamp TIMESTAMP(6) WITHOUT TIME ZONE, number_linked_element INTEGER, last_link_timestamp TIMESTAMP(6) WITHOUT TIME ZONE, sync_time TIMESTAMP(6) WITHOUT TIME ZONE NOT NULL, glossary_guid TEXT NOT NULL, PRIMARY KEY (term_guid, sync_time)); +COMMENT ON TABLE open_metadata.om_term_activity IS 'Term activity - '; +COMMENT ON COLUMN open_metadata.om_term_activity.last_feedback_timestamp IS 'Time of last feedback update on this term'; +COMMENT ON COLUMN open_metadata.om_term_activity.glossary_guid IS 'This is the owning glossary rather than where the term might show up.'; +DROP TABLE IF EXISTS open_metadata.om_todo; +CREATE TABLE open_metadata.om_todo (todo_guid TEXT NOT NULL, qualified_name TEXT, display_name TEXT, creation_time TIMESTAMP(6) WITHOUT TIME ZONE, todo_type TEXT, priority INTEGER, due_time TIMESTAMP(6) WITHOUT TIME ZONE, completion_time TIMESTAMP(6) WITHOUT TIME ZONE, status TEXT, todo_source_guid TEXT, todo_source_type TEXT, last_reviewed_time TIMESTAMP(6) WITHOUT TIME ZONE, sync_time TIMESTAMP(6) WITHOUT TIME ZONE NOT NULL, actor_guid TEXT, actor_type TEXT, CONSTRAINT om_todo_pk PRIMARY KEY (sync_time, todo_guid)); +COMMENT ON TABLE open_metadata.om_todo IS 'Represent all todos independent of source. The actual implementation of the todo might be in an external system, in which case we would map their external identifiers to our todos through the om_correlation_properties table.'; +COMMENT ON COLUMN open_metadata.om_todo.actor_guid IS 'The unique identifier of the actor assigned to perform this ToDo. An Actor is either a UserId, Profile, or PersonRole.'; +COMMENT ON COLUMN open_metadata.om_todo.actor_type IS 'Type name of actor'; +DROP TABLE IF EXISTS open_metadata.om_user_identity; +CREATE TABLE open_metadata.om_user_identity (employee_num CHARACTER VARYING(80), user_id CHARACTER VARYING(80) NOT NULL, preferred_name CHARACTER VARYING(80), org_name CHARACTER VARYING(80), resident_country CHARACTER VARYING(80), location CHARACTER VARYING(80), distinguished_name CHARACTER VARYING(80), user_id_guid CHARACTER VARYING(80) NOT NULL, profile_guid CHARACTER VARYING(80), department_id CHARACTER VARYING(40), sync_time TIMESTAMP(6) WITHOUT TIME ZONE NOT NULL, PRIMARY KEY (user_id_guid, sync_time)); +COMMENT ON TABLE open_metadata.om_user_identity IS 'registered users'; +DROP TABLE IF EXISTS open_metadata.rd_file_classifiers; +CREATE TABLE open_metadata.rd_file_classifiers (sr_guid TEXT NOT NULL, filename TEXT NOT NULL, file_extension TEXT, pathname TEXT NOT NULL, file_type TEXT, asset_type TEXT, deployed_implementation_type TEXT, encoding TEXT, sync_time TIMESTAMP(6) WITHOUT TIME ZONE NOT NULL, CONSTRAINT rd_file_classifiers_pk PRIMARY KEY (pathname, sync_time)); + + + diff --git a/egeria-platform-postgres-compose/egeria-platform-postgres.yaml b/egeria-platform-postgres-compose/egeria-platform-postgres.yaml new file mode 100644 index 0000000..45fcf8b --- /dev/null +++ b/egeria-platform-postgres-compose/egeria-platform-postgres.yaml @@ -0,0 +1,71 @@ +--- +# SPDX-License-Identifier: Apache-2.0 +# Copyright Contributors to the Egeria project + + +# To run +# * Ensure Docker is installed and running +# * Start Egeria Platform stack using +# 'docker-compose -f ./egeria-platform-postgres.yaml up' +# +# Assumptions: +# * Ports 9443 and 9092 are available on the host system - these are the default ports for Kafka and Egeria. +# * by default, no external volumes are used - comments below give examples of using external volumes +# * by default, Egeria will start with its set of default servers (active-metadata-store,view-server, +# integration-daemon,engine-host,simple-metadata-store) - you can change this. +# * by default, strict SSL validation is turned off +# +# See the following link for more info on volumes & why we need to use root: +# https://docs.bitnami.com/containers/how-to/work-with-non-root-containers + + +services: + kafka: + image: 'bitnami/kafka:latest' + ports: + - '9092:9092' + environment: + - KAFKA_CFG_NODE_ID=0 + - KAFKA_CFG_AUTO_CREATE_TOPICS_ENABLE=true + - KAFKA_CFG_PROCESS_ROLES=controller,broker + - KAFKA_CFG_LISTENERS=PLAINTEXT://:9092,CONTROLLER://:9093 + - KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP=CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT + - KAFKA_CFG_CONTROLLER_QUORUM_VOTERS=0@kafka:9093 + - KAFKA_CFG_CONTROLLER_LISTENER_NAMES=CONTROLLER + + egeria-platform: + depends_on: + - kafka + image: 'docker.io/odpi/egeria-platform:latest' + ports: + - '9443:9443' + # volumes: + # - "YOUR DATA DIRECTORY"/data:/deployments/data + # - "YOUR EXTRA LIBRARY DIRECTORY"/extra:/deployments/extra + # - "ANOTHER DIRECTOR TO MOUNT"/:/deployments/user_mount + + postgres: + image: postgres:latest + container_name: observations + restart: always + ports: + - "127.0.0.1:5432:5432" + + shm_size: 128mb + environment: + POSTGRES_PASSWORD: notingres + volumes: + - db_home:/var/lib/postgresql/data + - ./docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d + + +# +# Change external to true and create volumes manually if you wish to persist between runs +# +volumes: + zookeeper-data: + external: false + kafka-data: + external: false + db_home: + external: false diff --git a/gradle.properties b/gradle.properties deleted file mode 100644 index 8b6f356..0000000 --- a/gradle.properties +++ /dev/null @@ -1,30 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -# Copyright Contributors to the ODPi Egeria project. - -# Allow parallel builds (defaults will be 1 per CPU thread) -org.gradle.parallel=true - -# Caching -org.gradle.caching=true - -# Watch fileystem - disable - too many files swamps OS -org.gradle.vfs.watch=true - -# Default logging output -org.gradle.console=auto - -# Stop if we get a warning -org.gradle.warning.mode=summary - -# Defer configuration until needed -org.gradle.configureondemand=true - -# Increase memory to 4GB -org.gradle.jvmargs=-Xmx4096m - -# If system is quiet, all cpu will be used anyway. But on interactive desktop this will -# help keep system responsive whilst a build is running -org.gradle.priority=low - -# Timeout the daemon after an hour -org.gradle.daemon.idletimeout=3600000 diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index ccebba7710deaf9f98673a68957ea02138b60d0a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 61608 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 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index bdc9a83..0000000 --- a/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,6 +0,0 @@ -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.2-bin.zip -networkTimeout=10000 -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew deleted file mode 100755 index 79a61d4..0000000 --- a/gradlew +++ /dev/null @@ -1,244 +0,0 @@ -#!/bin/sh - -# -# Copyright © 2015-2021 the original authors. -# -# 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 -# -# https://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. -# - -############################################################################## -# -# Gradle start up script for POSIX generated by Gradle. -# -# Important for running: -# -# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is -# noncompliant, but you have some other compliant shell such as ksh or -# bash, then to run this script, type that shell name before the whole -# command line, like: -# -# ksh Gradle -# -# Busybox and similar reduced shells will NOT work, because this script -# requires all of these POSIX shell features: -# * functions; -# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», -# «${var#prefix}», «${var%suffix}», and «$( cmd )»; -# * compound commands having a testable exit status, especially «case»; -# * various built-in commands including «command», «set», and «ulimit». -# -# Important for patching: -# -# (2) This script targets any POSIX shell, so it avoids extensions provided -# by Bash, Ksh, etc; in particular arrays are avoided. -# -# The "traditional" practice of packing multiple parameters into a -# space-separated string is a well documented source of bugs and security -# problems, so this is (mostly) avoided, by progressively accumulating -# options in "$@", and eventually passing that to Java. -# -# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, -# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; -# see the in-line comments for details. -# -# There are tweaks for specific operating systems such as AIX, CygWin, -# Darwin, MinGW, and NonStop. -# -# (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt -# within the Gradle project. -# -# You can find Gradle at https://github.com/gradle/gradle/. -# -############################################################################## - -# Attempt to set APP_HOME - -# Resolve links: $0 may be a link -app_path=$0 - -# Need this for daisy-chained symlinks. -while - APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path - [ -h "$app_path" ] -do - ls=$( ls -ld "$app_path" ) - link=${ls#*' -> '} - case $link in #( - /*) app_path=$link ;; #( - *) app_path=$APP_HOME$link ;; - esac -done - -# This is normally unused -# shellcheck disable=SC2034 -APP_BASE_NAME=${0##*/} -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD=maximum - -warn () { - echo "$*" -} >&2 - -die () { - echo - echo "$*" - echo - exit 1 -} >&2 - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -nonstop=false -case "$( uname )" in #( - CYGWIN* ) cygwin=true ;; #( - Darwin* ) darwin=true ;; #( - MSYS* | MINGW* ) msys=true ;; #( - NONSTOP* ) nonstop=true ;; -esac - -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar - - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD=$JAVA_HOME/jre/sh/java - else - JAVACMD=$JAVA_HOME/bin/java - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." -fi - -# Increase the maximum file descriptors if we can. -if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then - case $MAX_FD in #( - max*) - # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 - MAX_FD=$( ulimit -H -n ) || - warn "Could not query maximum file descriptor limit" - esac - case $MAX_FD in #( - '' | soft) :;; #( - *) - # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 - ulimit -n "$MAX_FD" || - warn "Could not set maximum file descriptor limit to $MAX_FD" - esac -fi - -# Collect all arguments for the java command, stacking in reverse order: -# * args from the command line -# * the main class name -# * -classpath -# * -D...appname settings -# * --module-path (only if needed) -# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. - -# For Cygwin or MSYS, switch paths to Windows format before running java -if "$cygwin" || "$msys" ; then - APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) - - JAVACMD=$( cygpath --unix "$JAVACMD" ) - - # Now convert the arguments - kludge to limit ourselves to /bin/sh - for arg do - if - case $arg in #( - -*) false ;; # don't mess with options #( - /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath - [ -e "$t" ] ;; #( - *) false ;; - esac - then - arg=$( cygpath --path --ignore --mixed "$arg" ) - fi - # Roll the args list around exactly as many times as the number of - # args, so each arg winds up back in the position where it started, but - # possibly modified. - # - # NB: a `for` loop captures its iteration list before it begins, so - # changing the positional parameters here affects neither the number of - # iterations, nor the values presented in `arg`. - shift # remove old arg - set -- "$@" "$arg" # push replacement arg - done -fi - -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. - -set -- \ - "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ - "$@" - -# Stop when "xargs" is not available. -if ! command -v xargs >/dev/null 2>&1 -then - die "xargs is not available" -fi - -# Use "xargs" to parse quoted args. -# -# With -n1 it outputs one arg per line, with the quotes and backslashes removed. -# -# In Bash we could simply go: -# -# readarray ARGS < <( xargs -n1 <<<"$var" ) && -# set -- "${ARGS[@]}" "$@" -# -# but POSIX shell has neither arrays nor command substitution, so instead we -# post-process each arg (as a line of input to sed) to backslash-escape any -# character that might be a shell metacharacter, then use eval to reverse -# that process (while maintaining the separation between arguments), and wrap -# the whole thing up as a single "set" statement. -# -# This will of course break if any of these variables contains a newline or -# an unmatched quote. -# - -eval "set -- $( - printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | - xargs -n1 | - sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | - tr '\n' ' ' - )" '"$@"' - -exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat deleted file mode 100644 index 6689b85..0000000 --- a/gradlew.bat +++ /dev/null @@ -1,92 +0,0 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem - -@if "%DEBUG%"=="" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%"=="" set DIRNAME=. -@rem This is normally unused -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if %ERRORLEVEL% equ 0 goto execute - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* - -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..6518d8b --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,18 @@ +[tool.poetry] +name = "egeria-workspaces" +version = "0.1.0" +description = "" +authors = ["Dan Wolfson "] +readme = "README.md" + +[tool.poetry.dependencies] +python = "^3.12" +jupyter = "^1.0.0" +rich = "^13.7.1" +pyegeria = "^0.5.5.9" +unitycatalog = "^0.1.1" + + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" diff --git a/settings.gradle b/settings.gradle deleted file mode 100644 index a4db8ac..0000000 --- a/settings.gradle +++ /dev/null @@ -1,19 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * Copyright Contributors to the ODPi Egeria project. - */ - -// Can pin plugin versions here as needed -pluginManagement { - plugins { - } -} -// TODO: Specify the name of the artifact (project name) here -rootProject.name = 'egeria-template-sample' - -// For multi modules, list each *project name* here -// include(':egeria-connector-postgres') - -// These define the project names. For now we'll keep them the same as the directory -// Not needed if the project name desired exactly matches the directory name -//project(':egeria-connector-postgres').projectDir = file('egeria-connector-postgres-directory') diff --git a/src/main/java/org/odpi/openmetadata/placeholder/Sample.java b/src/main/java/org/odpi/openmetadata/placeholder/Sample.java deleted file mode 100644 index 1be5126..0000000 --- a/src/main/java/org/odpi/openmetadata/placeholder/Sample.java +++ /dev/null @@ -1,10 +0,0 @@ -/* SPDX-License-Identifier: Apache-2.0 */ -/* Copyright Contributors to the ODPi Egeria project. */ - -package org.odpi.openmetadata.placeholder; - -/** - * TODO: Sample has no implementation - you need to replace this code - */ -public class Sample { -} diff --git a/src/test/java/org/odpi/openmetadata/placeholder/SampleTest.java b/src/test/java/org/odpi/openmetadata/placeholder/SampleTest.java deleted file mode 100644 index c42ebc1..0000000 --- a/src/test/java/org/odpi/openmetadata/placeholder/SampleTest.java +++ /dev/null @@ -1,21 +0,0 @@ -/* SPDX-License-Identifier: Apache-2.0 */ -/* Copyright Contributors to the ODPi Egeria project. */ - -// TODO: Remove placeholder test code & replace with real code -package org.odpi.openmetadata.placeholder; - -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertTrue; - - -/** - * KafkaMonitorIntegrationConnector catalogues active topics in a Strimzi broker. - */ -public class SampleTest { - - @Test - public void testSample() { - assertTrue(true); - } -} diff --git a/workspaces/Survey & Explore.ipynb b/workspaces/Survey & Explore.ipynb new file mode 100644 index 0000000..e09b952 --- /dev/null +++ b/workspaces/Survey & Explore.ipynb @@ -0,0 +1,76 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 3, + "id": "34986bff-1bd8-4a66-bf10-dd1a17042593", + "metadata": {}, + "outputs": [], + "source": [ + "from pyegeria import AutomatedCuration\n", + "import asyncio\n", + "import nest_asyncio\n", + "nest_asyncio.apply()" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "5304b227-0d6e-4789-b34e-05773f771ab4", + "metadata": {}, + "outputs": [ + { + "ename": "InvalidParameterException", + "evalue": "A client-side error 400 was received by method __step from API call Client during the call https://host.docker.internal:9443/servers/active-metadata-store/open-metadata/framework-services/asset-owner/open-metadata-store/users/garygeeke/metadata-elements/by-search-string?startFrom=start_from&pageSize=page_size. The error message was CLIENT-SIDE-REST-API-CONNECTOR-503-002", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mInvalidParameterException\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[5], line 3\u001b[0m\n\u001b[1;32m 1\u001b[0m p \u001b[38;5;241m=\u001b[39m LoadedResources(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mactive-metadata-store\u001b[39m\u001b[38;5;124m\"\u001b[39m,\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mhttps://host.docker.internal:9443\u001b[39m\u001b[38;5;124m\"\u001b[39m, \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mgarygeeke\u001b[39m\u001b[38;5;124m\"\u001b[39m, \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124msecret\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[1;32m 2\u001b[0m token \u001b[38;5;241m=\u001b[39m p\u001b[38;5;241m.\u001b[39mcreate_egeria_bearer_token()\n\u001b[0;32m----> 3\u001b[0m \u001b[43mp\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mget_all_templates\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n", + "File \u001b[0;32m/opt/conda/lib/python3.11/site-packages/pyegeria/Xloaded_resources_omvs.py:88\u001b[0m, in \u001b[0;36mLoadedResources.get_all_templates\u001b[0;34m(self, server, start_from, page_size)\u001b[0m\n\u001b[1;32m 82\u001b[0m url \u001b[38;5;241m=\u001b[39m (\u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;132;01m{\u001b[39;00m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mplatform_url\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m/servers/\u001b[39m\u001b[38;5;132;01m{\u001b[39;00mserver\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m/open-metadata/framework-services/asset-owner/open-metadata-store/\u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[1;32m 83\u001b[0m \u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124musers/\u001b[39m\u001b[38;5;132;01m{\u001b[39;00m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39muser_id\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m/metadata-elements/by-search-string?startFrom=start_from&pageSize=page_size\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[1;32m 84\u001b[0m body \u001b[38;5;241m=\u001b[39m {\n\u001b[1;32m 85\u001b[0m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mclass\u001b[39m\u001b[38;5;124m\"\u001b[39m : \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mSearchStringRequestBody\u001b[39m\u001b[38;5;124m\"\u001b[39m,\n\u001b[1;32m 86\u001b[0m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124msearchString\u001b[39m\u001b[38;5;124m\"\u001b[39m : \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m.*Template.*\u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[1;32m 87\u001b[0m }\n\u001b[0;32m---> 88\u001b[0m response \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mmake_request\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mPOST\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43murl\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mbody\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 89\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m response\u001b[38;5;241m.\u001b[39mjson()\u001b[38;5;241m.\u001b[39mget(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124melementList\u001b[39m\u001b[38;5;124m\"\u001b[39m, \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mNo elements\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n", + "File \u001b[0;32m/opt/conda/lib/python3.11/site-packages/pyegeria/_client.py:338\u001b[0m, in \u001b[0;36mClient.make_request\u001b[0;34m(self, request_type, endpoint, payload, time_out)\u001b[0m\n\u001b[1;32m 336\u001b[0m \u001b[38;5;250m\u001b[39m\u001b[38;5;124;03m\"\"\" Make a request to the Egeria API\"\"\"\u001b[39;00m\n\u001b[1;32m 337\u001b[0m loop \u001b[38;5;241m=\u001b[39m asyncio\u001b[38;5;241m.\u001b[39mget_event_loop()\n\u001b[0;32m--> 338\u001b[0m response \u001b[38;5;241m=\u001b[39m \u001b[43mloop\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mrun_until_complete\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_async_make_request\u001b[49m\u001b[43m(\u001b[49m\u001b[43mrequest_type\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mendpoint\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 339\u001b[0m \u001b[43m \u001b[49m\u001b[43mpayload\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mtime_out\u001b[49m\u001b[43m)\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 340\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m response\n", + "File \u001b[0;32m/opt/conda/lib/python3.11/site-packages/nest_asyncio.py:98\u001b[0m, in \u001b[0;36m_patch_loop..run_until_complete\u001b[0;34m(self, future)\u001b[0m\n\u001b[1;32m 95\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m f\u001b[38;5;241m.\u001b[39mdone():\n\u001b[1;32m 96\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mRuntimeError\u001b[39;00m(\n\u001b[1;32m 97\u001b[0m \u001b[38;5;124m'\u001b[39m\u001b[38;5;124mEvent loop stopped before Future completed.\u001b[39m\u001b[38;5;124m'\u001b[39m)\n\u001b[0;32m---> 98\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mf\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mresult\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n", + "File \u001b[0;32m/opt/conda/lib/python3.11/asyncio/futures.py:203\u001b[0m, in \u001b[0;36mFuture.result\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 201\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m__log_traceback \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mFalse\u001b[39;00m\n\u001b[1;32m 202\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_exception \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[0;32m--> 203\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_exception\u001b[38;5;241m.\u001b[39mwith_traceback(\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_exception_tb)\n\u001b[1;32m 204\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_result\n", + "File \u001b[0;32m/opt/conda/lib/python3.11/asyncio/tasks.py:277\u001b[0m, in \u001b[0;36mTask.__step\u001b[0;34m(***failed resolving arguments***)\u001b[0m\n\u001b[1;32m 273\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[1;32m 274\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m exc \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[1;32m 275\u001b[0m \u001b[38;5;66;03m# We use the `send` method directly, because coroutines\u001b[39;00m\n\u001b[1;32m 276\u001b[0m \u001b[38;5;66;03m# don't have `__iter__` and `__next__` methods.\u001b[39;00m\n\u001b[0;32m--> 277\u001b[0m result \u001b[38;5;241m=\u001b[39m coro\u001b[38;5;241m.\u001b[39msend(\u001b[38;5;28;01mNone\u001b[39;00m)\n\u001b[1;32m 278\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[1;32m 279\u001b[0m result \u001b[38;5;241m=\u001b[39m coro\u001b[38;5;241m.\u001b[39mthrow(exc)\n", + "File \u001b[0;32m/opt/conda/lib/python3.11/site-packages/pyegeria/_client.py:480\u001b[0m, in \u001b[0;36mClient._async_make_request\u001b[0;34m(self, request_type, endpoint, payload, time_out)\u001b[0m\n\u001b[1;32m 478\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m UserNotAuthorizedException(exc_msg)\n\u001b[1;32m 479\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[0;32m--> 480\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m InvalidParameterException(exc_msg)\n\u001b[1;32m 482\u001b[0m \u001b[38;5;28;01melif\u001b[39;00m response\u001b[38;5;241m.\u001b[39mstatus_code \u001b[38;5;129;01min\u001b[39;00m (\u001b[38;5;241m500\u001b[39m, \u001b[38;5;241m501\u001b[39m, \u001b[38;5;241m502\u001b[39m, \u001b[38;5;241m503\u001b[39m, \u001b[38;5;241m504\u001b[39m):\n\u001b[1;32m 483\u001b[0m \u001b[38;5;66;03m# server errors\u001b[39;00m\n\u001b[1;32m 484\u001b[0m msg \u001b[38;5;241m=\u001b[39m OMAGCommonErrorCode\u001b[38;5;241m.\u001b[39mEXCEPTION_RESPONSE_FROM_API\u001b[38;5;241m.\u001b[39mvalue[\n\u001b[1;32m 485\u001b[0m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mmessage_template\u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[1;32m 486\u001b[0m ]\u001b[38;5;241m.\u001b[39mformat(\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 490\u001b[0m OMAGCommonErrorCode\u001b[38;5;241m.\u001b[39mEXCEPTION_RESPONSE_FROM_API\u001b[38;5;241m.\u001b[39mvalue[\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mmessage_id\u001b[39m\u001b[38;5;124m\"\u001b[39m],\n\u001b[1;32m 491\u001b[0m ) \u001b[38;5;241m+\u001b[39m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m==>System reports:\u001b[39m\u001b[38;5;124m'\u001b[39m\u001b[38;5;124m\"\u001b[39m \u001b[38;5;241m+\u001b[39m response\u001b[38;5;241m.\u001b[39mreason_phrase \u001b[38;5;241m+\u001b[39m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m'\u001b[39m\u001b[38;5;124m\"\u001b[39m\n", + "\u001b[0;31mInvalidParameterException\u001b[0m: A client-side error 400 was received by method __step from API call Client during the call https://host.docker.internal:9443/servers/active-metadata-store/open-metadata/framework-services/asset-owner/open-metadata-store/users/garygeeke/metadata-elements/by-search-string?startFrom=start_from&pageSize=page_size. The error message was CLIENT-SIDE-REST-API-CONNECTOR-503-002" + ] + } + ], + "source": [ + "p = LoadedResources(\"active-metadata-store\",\"https://host.docker.internal:9443\", \"garygeeke\", \"secret\")\n", + "token = p.create_egeria_bearer_token()\n", + "p.get_all_templates()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cd0e31a0-b190-46ba-be20-f647e686d6b7", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.9" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/workspaces/Untitled.ipynb b/workspaces/Untitled.ipynb new file mode 100644 index 0000000..5e9f891 --- /dev/null +++ b/workspaces/Untitled.ipynb @@ -0,0 +1,97 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "2094e0fc-29c6-4f33-b40e-e67d4f046a5f", + "metadata": {}, + "outputs": [], + "source": [ + "from pyegeria import AutomatedCuration" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "5114abc1-ef5f-49a5-a097-745b82b6cf45", + "metadata": {}, + "outputs": [], + "source": [ + "import asyncio\n", + "import nest_asyncio\n", + "nest_asyncio.apply()" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "1cb9b8e5-2672-4ae1-a8a3-7ef368ef368c", + "metadata": {}, + "outputs": [], + "source": [ + "client = AutomatedCuration('view-server','https://host.docker.internal:9443','erinoverview','secret')\n", + "token = client.create_egeria_bearer_token() " + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "458d6da3-bfb8-4f4c-80eb-84f28505d910", + "metadata": {}, + "outputs": [], + "source": [ + "client = client.get_engine_actions()\n" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "0b614b04-fe66-4f85-b027-57625a0c9127", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'No elements'" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "client" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3fffc1c3-ee0a-4b4f-8bf1-b3b67cc41158", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.9" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/workspaces/experiments.ipynb b/workspaces/experiments.ipynb new file mode 100644 index 0000000..ce91dcc --- /dev/null +++ b/workspaces/experiments.ipynb @@ -0,0 +1,734 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "9968014f-4cd4-40fb-959a-13689f5ac01e", + "metadata": { + "ExecuteTime": { + "end_time": "2024-07-18T00:23:20.558488Z", + "start_time": "2024-07-18T00:23:20.551811Z" + }, + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "import asyncio\n", + "import nest_asyncio\n", + "nest_asyncio.apply()" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "a11585d4-48e0-43c8-aba9-0bbc1b1cbbc1", + "metadata": { + "ExecuteTime": { + "end_time": "2024-07-18T00:23:22.658459Z", + "start_time": "2024-07-18T00:23:22.484398Z" + } + }, + "outputs": [], + "source": [ + "from pyegeria import Platform\n" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "271affe2-3f66-46cf-b3ec-75fe4e3d2cfe", + "metadata": { + "ExecuteTime": { + "end_time": "2024-07-18T00:23:24.245215Z", + "start_time": "2024-07-18T00:23:24.202681Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "'Egeria OMAG Server Platform (version 5.1-SNAPSHOT)\\n'" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "p_client = Platform('active-metadata-store', \"https://host.docker.internal:9443\",\"garygeeke\")\n", + "p_client.get_platform_origin() " + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "802fe1f8-9bd5-4378-ae46-2a677a8466b7", + "metadata": { + "ExecuteTime": { + "end_time": "2024-07-18T00:27:13.308981Z", + "start_time": "2024-07-18T00:27:10.147943Z" + }, + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n"
+      ],
+      "text/plain": []
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "\n"
+     ]
+    },
+    {
+     "data": {
+      "text/html": [
+       "
╭─────────────────────────────── Traceback (most recent call last) ────────────────────────────────╮\n",
+       " /opt/conda/lib/python3.11/site-packages/httpx/_transports/default.py:69 in                       \n",
+       " map_httpcore_exceptions                                                                          \n",
+       "                                                                                                  \n",
+       "    66 @contextlib.contextmanager                                                                 \n",
+       "    67 def map_httpcore_exceptions() -> typing.Iterator[None]:                                    \n",
+       "    68 try:                                                                                   \n",
+       "  69 │   │   yield                                                                              \n",
+       "    70 except Exception as exc:                                                               \n",
+       "    71 │   │   mapped_exc = None                                                                  \n",
+       "    72                                                                                            \n",
+       "                                                                                                  \n",
+       " /opt/conda/lib/python3.11/site-packages/httpx/_transports/default.py:233 in handle_request       \n",
+       "                                                                                                  \n",
+       "   230 │   │   │   extensions=request.extensions,                                                 \n",
+       "   231 │   │   )                                                                                  \n",
+       "   232 │   │   with map_httpcore_exceptions():                                                    \n",
+       " 233 │   │   │   resp = self._pool.handle_request(req)                                          \n",
+       "   234 │   │                                                                                      \n",
+       "   235 │   │   assert isinstance(resp.stream, typing.Iterable)                                    \n",
+       "   236                                                                                            \n",
+       "                                                                                                  \n",
+       " /opt/conda/lib/python3.11/site-packages/httpcore/_sync/connection_pool.py:216 in handle_request  \n",
+       "                                                                                                  \n",
+       "   213 │   │   │   │   closing = self._assign_requests_to_connections()                           \n",
+       "   214 │   │   │                                                                                  \n",
+       "   215 │   │   │   self._close_connections(closing)                                               \n",
+       " 216 │   │   │   raise exc from None                                                            \n",
+       "   217 │   │                                                                                      \n",
+       "   218 │   │   # Return the response. Note that in this case we still have to manage              \n",
+       "   219 │   │   # the point at which the response is closed.                                       \n",
+       "                                                                                                  \n",
+       " /opt/conda/lib/python3.11/site-packages/httpcore/_sync/connection_pool.py:196 in handle_request  \n",
+       "                                                                                                  \n",
+       "   193 │   │   │   │                                                                              \n",
+       "   194 │   │   │   │   try:                                                                       \n",
+       "   195 │   │   │   │   │   # Send the request on the assigned connection.                         \n",
+       " 196 │   │   │   │   │   response = connection.handle_request(                                  \n",
+       "   197 │   │   │   │   │   │   pool_request.request                                               \n",
+       "   198 │   │   │   │   │   )                                                                      \n",
+       "   199 │   │   │   │   except ConnectionNotAvailable:                                             \n",
+       "                                                                                                  \n",
+       " /opt/conda/lib/python3.11/site-packages/httpcore/_sync/connection.py:99 in handle_request        \n",
+       "                                                                                                  \n",
+       "    96 │   │   │   │   │   │   )                                                                  \n",
+       "    97 │   │   except BaseException as exc:                                                       \n",
+       "    98 │   │   │   self._connect_failed = True                                                    \n",
+       "  99 │   │   │   raise exc                                                                      \n",
+       "   100 │   │                                                                                      \n",
+       "   101 │   │   return self._connection.handle_request(request)                                    \n",
+       "   102                                                                                            \n",
+       "                                                                                                  \n",
+       " /opt/conda/lib/python3.11/site-packages/httpcore/_sync/connection.py:76 in handle_request        \n",
+       "                                                                                                  \n",
+       "    73 │   │   try:                                                                               \n",
+       "    74 │   │   │   with self._request_lock:                                                       \n",
+       "    75 │   │   │   │   if self._connection is None:                                               \n",
+       "  76 │   │   │   │   │   stream = self._connect(request)                                        \n",
+       "    77 │   │   │   │   │                                                                          \n",
+       "    78 │   │   │   │   │   ssl_object = stream.get_extra_info(\"ssl_object\")                       \n",
+       "    79 │   │   │   │   │   http2_negotiated = (                                                   \n",
+       "                                                                                                  \n",
+       " /opt/conda/lib/python3.11/site-packages/httpcore/_sync/connection.py:122 in _connect             \n",
+       "                                                                                                  \n",
+       "   119 │   │   │   │   │   │   \"socket_options\": self._socket_options,                            \n",
+       "   120 │   │   │   │   │   }                                                                      \n",
+       "   121 │   │   │   │   │   with Trace(\"connect_tcp\", logger, request, kwargs) as trace:           \n",
+       " 122 │   │   │   │   │   │   stream = self._network_backend.connect_tcp(**kwargs)               \n",
+       "   123 │   │   │   │   │   │   trace.return_value = stream                                        \n",
+       "   124 │   │   │   │   else:                                                                      \n",
+       "   125 │   │   │   │   │   kwargs = {                                                             \n",
+       "                                                                                                  \n",
+       " /opt/conda/lib/python3.11/site-packages/httpcore/_backends/sync.py:205 in connect_tcp            \n",
+       "                                                                                                  \n",
+       "   202 │   │   │   OSError: ConnectError,                                                         \n",
+       "   203 │   │   }                                                                                  \n",
+       "   204 │   │                                                                                      \n",
+       " 205 │   │   with map_exceptions(exc_map):                                                      \n",
+       "   206 │   │   │   sock = socket.create_connection(                                               \n",
+       "   207 │   │   │   │   address,                                                                   \n",
+       "   208 │   │   │   │   timeout,                                                                   \n",
+       "                                                                                                  \n",
+       " /opt/conda/lib/python3.11/contextlib.py:158 in __exit__                                          \n",
+       "                                                                                                  \n",
+       "   155 │   │   │   │   # tell if we get the same exception back                                   \n",
+       "   156 │   │   │   │   value = typ()                                                              \n",
+       "   157 │   │   │   try:                                                                           \n",
+       " 158 │   │   │   │   self.gen.throw(typ, value, traceback)                                      \n",
+       "   159 │   │   │   except StopIteration as exc:                                                   \n",
+       "   160 │   │   │   │   # Suppress StopIteration *unless* it's the same exception that             \n",
+       "   161 │   │   │   │   # was passed to throw().  This prevents a StopIteration                    \n",
+       "                                                                                                  \n",
+       " /opt/conda/lib/python3.11/site-packages/httpcore/_exceptions.py:14 in map_exceptions             \n",
+       "                                                                                                  \n",
+       "   11 except Exception as exc:  # noqa: PIE786                                                \n",
+       "   12 │   │   for from_exc, to_exc in map.items():                                                \n",
+       "   13 │   │   │   if isinstance(exc, from_exc):                                                   \n",
+       " 14 │   │   │   │   raise to_exc(exc) from exc                                                  \n",
+       "   15 │   │   raise  # pragma: nocover                                                            \n",
+       "   16                                                                                             \n",
+       "   17                                                                                             \n",
+       "╰──────────────────────────────────────────────────────────────────────────────────────────────────╯\n",
+       "ConnectError: [Errno 111] Connection refused\n",
+       "\n",
+       "The above exception was the direct cause of the following exception:\n",
+       "\n",
+       "╭─────────────────────────────── Traceback (most recent call last) ────────────────────────────────╮\n",
+       " /opt/conda/lib/python3.11/site-packages/unitycatalog/_base_client.py:951 in _request             \n",
+       "                                                                                                  \n",
+       "    948 │   │   log.debug(\"Sending HTTP Request: %s %s\", request.method, request.url)             \n",
+       "    949 │   │                                                                                     \n",
+       "    950 │   │   try:                                                                              \n",
+       "  951 │   │   │   response = self._client.send(                                                 \n",
+       "    952 │   │   │   │   request,                                                                  \n",
+       "    953 │   │   │   │   stream=stream or self._should_stream_response_body(request=request),      \n",
+       "    954 │   │   │   │   **kwargs,                                                                 \n",
+       "                                                                                                  \n",
+       " /opt/conda/lib/python3.11/site-packages/httpx/_client.py:914 in send                             \n",
+       "                                                                                                  \n",
+       "    911 │   │                                                                                     \n",
+       "    912 │   │   auth = self._build_request_auth(request, auth)                                    \n",
+       "    913 │   │                                                                                     \n",
+       "  914 │   │   response = self._send_handling_auth(                                              \n",
+       "    915 │   │   │   request,                                                                      \n",
+       "    916 │   │   │   auth=auth,                                                                    \n",
+       "    917 │   │   │   follow_redirects=follow_redirects,                                            \n",
+       "                                                                                                  \n",
+       " /opt/conda/lib/python3.11/site-packages/httpx/_client.py:942 in _send_handling_auth              \n",
+       "                                                                                                  \n",
+       "    939 │   │   │   request = next(auth_flow)                                                     \n",
+       "    940 │   │   │                                                                                 \n",
+       "    941 │   │   │   while True:                                                                   \n",
+       "  942 │   │   │   │   response = self._send_handling_redirects(                                 \n",
+       "    943 │   │   │   │   │   request,                                                              \n",
+       "    944 │   │   │   │   │   follow_redirects=follow_redirects,                                    \n",
+       "    945 │   │   │   │   │   history=history,                                                      \n",
+       "                                                                                                  \n",
+       " /opt/conda/lib/python3.11/site-packages/httpx/_client.py:979 in _send_handling_redirects         \n",
+       "                                                                                                  \n",
+       "    976 │   │   │   for hook in self._event_hooks[\"request\"]:                                     \n",
+       "    977 │   │   │   │   hook(request)                                                             \n",
+       "    978 │   │   │                                                                                 \n",
+       "  979 │   │   │   response = self._send_single_request(request)                                 \n",
+       "    980 │   │   │   try:                                                                          \n",
+       "    981 │   │   │   │   for hook in self._event_hooks[\"response\"]:                                \n",
+       "    982 │   │   │   │   │   hook(response)                                                        \n",
+       "                                                                                                  \n",
+       " /opt/conda/lib/python3.11/site-packages/httpx/_client.py:1015 in _send_single_request            \n",
+       "                                                                                                  \n",
+       "   1012 │   │   │   )                                                                             \n",
+       "   1013 │   │                                                                                     \n",
+       "   1014 │   │   with request_context(request=request):                                            \n",
+       " 1015 │   │   │   response = transport.handle_request(request)                                  \n",
+       "   1016 │   │                                                                                     \n",
+       "   1017 │   │   assert isinstance(response.stream, SyncByteStream)                                \n",
+       "   1018                                                                                           \n",
+       "                                                                                                  \n",
+       " /opt/conda/lib/python3.11/site-packages/httpx/_transports/default.py:232 in handle_request       \n",
+       "                                                                                                  \n",
+       "   229 │   │   │   content=request.stream,                                                        \n",
+       "   230 │   │   │   extensions=request.extensions,                                                 \n",
+       "   231 │   │   )                                                                                  \n",
+       " 232 │   │   with map_httpcore_exceptions():                                                    \n",
+       "   233 │   │   │   resp = self._pool.handle_request(req)                                          \n",
+       "   234 │   │                                                                                      \n",
+       "   235 │   │   assert isinstance(resp.stream, typing.Iterable)                                    \n",
+       "                                                                                                  \n",
+       " /opt/conda/lib/python3.11/contextlib.py:158 in __exit__                                          \n",
+       "                                                                                                  \n",
+       "   155 │   │   │   │   # tell if we get the same exception back                                   \n",
+       "   156 │   │   │   │   value = typ()                                                              \n",
+       "   157 │   │   │   try:                                                                           \n",
+       " 158 │   │   │   │   self.gen.throw(typ, value, traceback)                                      \n",
+       "   159 │   │   │   except StopIteration as exc:                                                   \n",
+       "   160 │   │   │   │   # Suppress StopIteration *unless* it's the same exception that             \n",
+       "   161 │   │   │   │   # was passed to throw().  This prevents a StopIteration                    \n",
+       "                                                                                                  \n",
+       " /opt/conda/lib/python3.11/site-packages/httpx/_transports/default.py:86 in                       \n",
+       " map_httpcore_exceptions                                                                          \n",
+       "                                                                                                  \n",
+       "    83 │   │   │   raise                                                                          \n",
+       "    84 │   │                                                                                      \n",
+       "    85 │   │   message = str(exc)                                                                 \n",
+       "  86 │   │   raise mapped_exc(message) from exc                                                 \n",
+       "    87                                                                                            \n",
+       "    88                                                                                            \n",
+       "    89 HTTPCORE_EXC_MAP = {                                                                       \n",
+       "╰──────────────────────────────────────────────────────────────────────────────────────────────────╯\n",
+       "ConnectError: [Errno 111] Connection refused\n",
+       "\n",
+       "The above exception was the direct cause of the following exception:\n",
+       "\n",
+       "╭─────────────────────────────── Traceback (most recent call last) ────────────────────────────────╮\n",
+       " /home/jovyan/workspaces/uc_oss_catalog_viewer.py:147 in display_catalog                          \n",
+       "                                                                                                  \n",
+       "   144 try:                                                                                   \n",
+       "   145 │   │   console = Console(width=200)                                                       \n",
+       "   146 │   │   with console.pager():                                                              \n",
+       " 147 │   │   │   console.print(generate_table())                                                \n",
+       "   148                                                                                        \n",
+       "   149 except Exception as e:                                                                 \n",
+       "   150 │   │   print()                                                                            \n",
+       "                                                                                                  \n",
+       " /home/jovyan/workspaces/uc_oss_catalog_viewer.py:49 in generate_table                            \n",
+       "                                                                                                  \n",
+       "    46 │   │   table.add_column(\"Vol/Tab\")                                                        \n",
+       "    47 │   │   table.add_column(\"Details\")                                                        \n",
+       "    48 │   │                                                                                      \n",
+       "  49 │   │   c_list = uc_client.catalogs.list()                                                 \n",
+       "    50 │   │   catalogs= c_list.catalogs                                                          \n",
+       "    51 │   │   for catalog in catalogs:                                                           \n",
+       "    52 │   │   │   # cat_info = catalog.CatalogInfo                                               \n",
+       "                                                                                                  \n",
+       " /opt/conda/lib/python3.11/site-packages/unitycatalog/resources/catalogs.py:211 in list           \n",
+       "                                                                                                  \n",
+       "   208 │   │                                                                                      \n",
+       "   209 │   │     timeout: Override the client-level default timeout for this request, in second   \n",
+       "   210 │   │   \"\"\"                                                                                \n",
+       " 211 │   │   return self._get(                                                                  \n",
+       "   212 │   │   │   \"/catalogs\",                                                                   \n",
+       "   213 │   │   │   options=make_request_options(                                                  \n",
+       "   214 │   │   │   │   extra_headers=extra_headers,                                               \n",
+       "                                                                                                  \n",
+       " /opt/conda/lib/python3.11/site-packages/unitycatalog/_base_client.py:1168 in get                 \n",
+       "                                                                                                  \n",
+       "   1165 │   │   opts = FinalRequestOptions.construct(method=\"get\", url=path, **options)           \n",
+       "   1166 │   │   # cast is required because mypy complains about returning Any even though         \n",
+       "   1167 │   │   # it understands the type variables                                               \n",
+       " 1168 │   │   return cast(ResponseT, self.request(cast_to, opts, stream=stream, stream_cls=str  \n",
+       "   1169                                                                                       \n",
+       "   1170 @overload                                                                             \n",
+       "   1171 def post(                                                                             \n",
+       "                                                                                                  \n",
+       " /opt/conda/lib/python3.11/site-packages/unitycatalog/_base_client.py:920 in request              \n",
+       "                                                                                                  \n",
+       "    917 │   │   stream: bool = False,                                                             \n",
+       "    918 │   │   stream_cls: type[_StreamT] | None = None,                                         \n",
+       "    919 ) -> ResponseT | _StreamT:                                                            \n",
+       "  920 │   │   return self._request(                                                             \n",
+       "    921 │   │   │   cast_to=cast_to,                                                              \n",
+       "    922 │   │   │   options=options,                                                              \n",
+       "    923 │   │   │   stream=stream,                                                                \n",
+       "                                                                                                  \n",
+       " /opt/conda/lib/python3.11/site-packages/unitycatalog/_base_client.py:975 in _request             \n",
+       "                                                                                                  \n",
+       "    972 │   │   │   log.debug(\"Encountered Exception\", exc_info=True)                             \n",
+       "    973 │   │   │                                                                                 \n",
+       "    974 │   │   │   if retries > 0:                                                               \n",
+       "  975 │   │   │   │   return self._retry_request(                                               \n",
+       "    976 │   │   │   │   │   options,                                                              \n",
+       "    977 │   │   │   │   │   cast_to,                                                              \n",
+       "    978 │   │   │   │   │   retries,                                                              \n",
+       "                                                                                                  \n",
+       " /opt/conda/lib/python3.11/site-packages/unitycatalog/_base_client.py:1051 in _retry_request      \n",
+       "                                                                                                  \n",
+       "   1048 │   │   # different thread if necessary.                                                  \n",
+       "   1049 │   │   time.sleep(timeout)                                                               \n",
+       "   1050 │   │                                                                                     \n",
+       " 1051 │   │   return self._request(                                                             \n",
+       "   1052 │   │   │   options=options,                                                              \n",
+       "   1053 │   │   │   cast_to=cast_to,                                                              \n",
+       "   1054 │   │   │   remaining_retries=remaining,                                                  \n",
+       "                                                                                                  \n",
+       " /opt/conda/lib/python3.11/site-packages/unitycatalog/_base_client.py:975 in _request             \n",
+       "                                                                                                  \n",
+       "    972 │   │   │   log.debug(\"Encountered Exception\", exc_info=True)                             \n",
+       "    973 │   │   │                                                                                 \n",
+       "    974 │   │   │   if retries > 0:                                                               \n",
+       "  975 │   │   │   │   return self._retry_request(                                               \n",
+       "    976 │   │   │   │   │   options,                                                              \n",
+       "    977 │   │   │   │   │   cast_to,                                                              \n",
+       "    978 │   │   │   │   │   retries,                                                              \n",
+       "                                                                                                  \n",
+       " /opt/conda/lib/python3.11/site-packages/unitycatalog/_base_client.py:1051 in _retry_request      \n",
+       "                                                                                                  \n",
+       "   1048 │   │   # different thread if necessary.                                                  \n",
+       "   1049 │   │   time.sleep(timeout)                                                               \n",
+       "   1050 │   │                                                                                     \n",
+       " 1051 │   │   return self._request(                                                             \n",
+       "   1052 │   │   │   options=options,                                                              \n",
+       "   1053 │   │   │   cast_to=cast_to,                                                              \n",
+       "   1054 │   │   │   remaining_retries=remaining,                                                  \n",
+       "                                                                                                  \n",
+       " /opt/conda/lib/python3.11/site-packages/unitycatalog/_base_client.py:985 in _request             \n",
+       "                                                                                                  \n",
+       "    982 │   │   │   │   )                                                                         \n",
+       "    983 │   │   │                                                                                 \n",
+       "    984 │   │   │   log.debug(\"Raising connection error\")                                         \n",
+       "  985 │   │   │   raise APIConnectionError(request=request) from err                            \n",
+       "    986 │   │                                                                                     \n",
+       "    987 │   │   log.debug(                                                                        \n",
+       "    988 │   │   │   'HTTP Response: %s %s \"%i %s\" %s',                                            \n",
+       "╰──────────────────────────────────────────────────────────────────────────────────────────────────╯\n",
+       "APIConnectionError: Connection error.\n",
+       "
\n" + ], + "text/plain": [ + "\u001b[31m╭─\u001b[0m\u001b[31m──────────────────────────────\u001b[0m\u001b[31m \u001b[0m\u001b[1;31mTraceback \u001b[0m\u001b[1;2;31m(most recent call last)\u001b[0m\u001b[31m \u001b[0m\u001b[31m───────────────────────────────\u001b[0m\u001b[31m─╮\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2;33m/opt/conda/lib/python3.11/site-packages/httpx/_transports/\u001b[0m\u001b[1;33mdefault.py\u001b[0m:\u001b[94m69\u001b[0m in \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[92mmap_httpcore_exceptions\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 66 \u001b[0m\u001b[1;95m@contextlib\u001b[0m.contextmanager \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 67 \u001b[0m\u001b[94mdef\u001b[0m \u001b[92mmap_httpcore_exceptions\u001b[0m() -> typing.Iterator[\u001b[94mNone\u001b[0m]: \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 68 \u001b[0m\u001b[2m│ \u001b[0m\u001b[94mtry\u001b[0m: \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m❱ \u001b[0m 69 \u001b[2m│ │ \u001b[0m\u001b[94myield\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 70 \u001b[0m\u001b[2m│ \u001b[0m\u001b[94mexcept\u001b[0m \u001b[96mException\u001b[0m \u001b[94mas\u001b[0m exc: \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 71 \u001b[0m\u001b[2m│ │ \u001b[0mmapped_exc = \u001b[94mNone\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 72 \u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2;33m/opt/conda/lib/python3.11/site-packages/httpx/_transports/\u001b[0m\u001b[1;33mdefault.py\u001b[0m:\u001b[94m233\u001b[0m in \u001b[92mhandle_request\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m230 \u001b[0m\u001b[2m│ │ │ \u001b[0mextensions=request.extensions, \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m231 \u001b[0m\u001b[2m│ │ \u001b[0m) \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m232 \u001b[0m\u001b[2m│ │ \u001b[0m\u001b[94mwith\u001b[0m map_httpcore_exceptions(): \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m❱ \u001b[0m233 \u001b[2m│ │ │ \u001b[0mresp = \u001b[96mself\u001b[0m._pool.handle_request(req) \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m234 \u001b[0m\u001b[2m│ │ \u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m235 \u001b[0m\u001b[2m│ │ \u001b[0m\u001b[94massert\u001b[0m \u001b[96misinstance\u001b[0m(resp.stream, typing.Iterable) \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m236 \u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2;33m/opt/conda/lib/python3.11/site-packages/httpcore/_sync/\u001b[0m\u001b[1;33mconnection_pool.py\u001b[0m:\u001b[94m216\u001b[0m in \u001b[92mhandle_request\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m213 \u001b[0m\u001b[2m│ │ │ │ \u001b[0mclosing = \u001b[96mself\u001b[0m._assign_requests_to_connections() \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m214 \u001b[0m\u001b[2m│ │ │ \u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m215 \u001b[0m\u001b[2m│ │ │ \u001b[0m\u001b[96mself\u001b[0m._close_connections(closing) \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m❱ \u001b[0m216 \u001b[2m│ │ │ \u001b[0m\u001b[94mraise\u001b[0m exc \u001b[94mfrom\u001b[0m \u001b[94mNone\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m217 \u001b[0m\u001b[2m│ │ \u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m218 \u001b[0m\u001b[2m│ │ \u001b[0m\u001b[2m# Return the response. Note that in this case we still have to manage\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m219 \u001b[0m\u001b[2m│ │ \u001b[0m\u001b[2m# the point at which the response is closed.\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2;33m/opt/conda/lib/python3.11/site-packages/httpcore/_sync/\u001b[0m\u001b[1;33mconnection_pool.py\u001b[0m:\u001b[94m196\u001b[0m in \u001b[92mhandle_request\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m193 \u001b[0m\u001b[2m│ │ │ │ \u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m194 \u001b[0m\u001b[2m│ │ │ │ \u001b[0m\u001b[94mtry\u001b[0m: \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m195 \u001b[0m\u001b[2m│ │ │ │ │ \u001b[0m\u001b[2m# Send the request on the assigned connection.\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m❱ \u001b[0m196 \u001b[2m│ │ │ │ │ \u001b[0mresponse = connection.handle_request( \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m197 \u001b[0m\u001b[2m│ │ │ │ │ │ \u001b[0mpool_request.request \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m198 \u001b[0m\u001b[2m│ │ │ │ │ \u001b[0m) \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m199 \u001b[0m\u001b[2m│ │ │ │ \u001b[0m\u001b[94mexcept\u001b[0m ConnectionNotAvailable: \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2;33m/opt/conda/lib/python3.11/site-packages/httpcore/_sync/\u001b[0m\u001b[1;33mconnection.py\u001b[0m:\u001b[94m99\u001b[0m in \u001b[92mhandle_request\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 96 \u001b[0m\u001b[2m│ │ │ │ │ │ \u001b[0m) \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 97 \u001b[0m\u001b[2m│ │ \u001b[0m\u001b[94mexcept\u001b[0m \u001b[96mBaseException\u001b[0m \u001b[94mas\u001b[0m exc: \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 98 \u001b[0m\u001b[2m│ │ │ \u001b[0m\u001b[96mself\u001b[0m._connect_failed = \u001b[94mTrue\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m❱ \u001b[0m 99 \u001b[2m│ │ │ \u001b[0m\u001b[94mraise\u001b[0m exc \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m100 \u001b[0m\u001b[2m│ │ \u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m101 \u001b[0m\u001b[2m│ │ \u001b[0m\u001b[94mreturn\u001b[0m \u001b[96mself\u001b[0m._connection.handle_request(request) \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m102 \u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2;33m/opt/conda/lib/python3.11/site-packages/httpcore/_sync/\u001b[0m\u001b[1;33mconnection.py\u001b[0m:\u001b[94m76\u001b[0m in \u001b[92mhandle_request\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 73 \u001b[0m\u001b[2m│ │ \u001b[0m\u001b[94mtry\u001b[0m: \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 74 \u001b[0m\u001b[2m│ │ │ \u001b[0m\u001b[94mwith\u001b[0m \u001b[96mself\u001b[0m._request_lock: \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 75 \u001b[0m\u001b[2m│ │ │ │ \u001b[0m\u001b[94mif\u001b[0m \u001b[96mself\u001b[0m._connection \u001b[95mis\u001b[0m \u001b[94mNone\u001b[0m: \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m❱ \u001b[0m 76 \u001b[2m│ │ │ │ │ \u001b[0mstream = \u001b[96mself\u001b[0m._connect(request) \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 77 \u001b[0m\u001b[2m│ │ │ │ │ \u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 78 \u001b[0m\u001b[2m│ │ │ │ │ \u001b[0mssl_object = stream.get_extra_info(\u001b[33m\"\u001b[0m\u001b[33mssl_object\u001b[0m\u001b[33m\"\u001b[0m) \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 79 \u001b[0m\u001b[2m│ │ │ │ │ \u001b[0mhttp2_negotiated = ( \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2;33m/opt/conda/lib/python3.11/site-packages/httpcore/_sync/\u001b[0m\u001b[1;33mconnection.py\u001b[0m:\u001b[94m122\u001b[0m in \u001b[92m_connect\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m119 \u001b[0m\u001b[2m│ │ │ │ │ │ \u001b[0m\u001b[33m\"\u001b[0m\u001b[33msocket_options\u001b[0m\u001b[33m\"\u001b[0m: \u001b[96mself\u001b[0m._socket_options, \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m120 \u001b[0m\u001b[2m│ │ │ │ │ \u001b[0m} \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m121 \u001b[0m\u001b[2m│ │ │ │ │ \u001b[0m\u001b[94mwith\u001b[0m Trace(\u001b[33m\"\u001b[0m\u001b[33mconnect_tcp\u001b[0m\u001b[33m\"\u001b[0m, logger, request, kwargs) \u001b[94mas\u001b[0m trace: \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m❱ \u001b[0m122 \u001b[2m│ │ │ │ │ │ \u001b[0mstream = \u001b[96mself\u001b[0m._network_backend.connect_tcp(**kwargs) \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m123 \u001b[0m\u001b[2m│ │ │ │ │ │ \u001b[0mtrace.return_value = stream \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m124 \u001b[0m\u001b[2m│ │ │ │ \u001b[0m\u001b[94melse\u001b[0m: \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m125 \u001b[0m\u001b[2m│ │ │ │ │ \u001b[0mkwargs = { \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2;33m/opt/conda/lib/python3.11/site-packages/httpcore/_backends/\u001b[0m\u001b[1;33msync.py\u001b[0m:\u001b[94m205\u001b[0m in \u001b[92mconnect_tcp\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m202 \u001b[0m\u001b[2m│ │ │ \u001b[0m\u001b[96mOSError\u001b[0m: ConnectError, \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m203 \u001b[0m\u001b[2m│ │ \u001b[0m} \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m204 \u001b[0m\u001b[2m│ │ \u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m❱ \u001b[0m205 \u001b[2m│ │ \u001b[0m\u001b[94mwith\u001b[0m map_exceptions(exc_map): \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m206 \u001b[0m\u001b[2m│ │ │ \u001b[0msock = socket.create_connection( \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m207 \u001b[0m\u001b[2m│ │ │ │ \u001b[0maddress, \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m208 \u001b[0m\u001b[2m│ │ │ │ \u001b[0mtimeout, \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2;33m/opt/conda/lib/python3.11/\u001b[0m\u001b[1;33mcontextlib.py\u001b[0m:\u001b[94m158\u001b[0m in \u001b[92m__exit__\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m155 \u001b[0m\u001b[2m│ │ │ │ \u001b[0m\u001b[2m# tell if we get the same exception back\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m156 \u001b[0m\u001b[2m│ │ │ │ \u001b[0mvalue = typ() \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m157 \u001b[0m\u001b[2m│ │ │ \u001b[0m\u001b[94mtry\u001b[0m: \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m❱ \u001b[0m158 \u001b[2m│ │ │ │ \u001b[0m\u001b[96mself\u001b[0m.gen.throw(typ, value, traceback) \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m159 \u001b[0m\u001b[2m│ │ │ \u001b[0m\u001b[94mexcept\u001b[0m \u001b[96mStopIteration\u001b[0m \u001b[94mas\u001b[0m exc: \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m160 \u001b[0m\u001b[2m│ │ │ │ \u001b[0m\u001b[2m# Suppress StopIteration *unless* it's the same exception that\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m161 \u001b[0m\u001b[2m│ │ │ │ \u001b[0m\u001b[2m# was passed to throw(). This prevents a StopIteration\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2;33m/opt/conda/lib/python3.11/site-packages/httpcore/\u001b[0m\u001b[1;33m_exceptions.py\u001b[0m:\u001b[94m14\u001b[0m in \u001b[92mmap_exceptions\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m11 \u001b[0m\u001b[2m│ \u001b[0m\u001b[94mexcept\u001b[0m \u001b[96mException\u001b[0m \u001b[94mas\u001b[0m exc: \u001b[2m# noqa: PIE786\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m12 \u001b[0m\u001b[2m│ │ \u001b[0m\u001b[94mfor\u001b[0m from_exc, to_exc \u001b[95min\u001b[0m \u001b[96mmap\u001b[0m.items(): \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m13 \u001b[0m\u001b[2m│ │ │ \u001b[0m\u001b[94mif\u001b[0m \u001b[96misinstance\u001b[0m(exc, from_exc): \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m❱ \u001b[0m14 \u001b[2m│ │ │ │ \u001b[0m\u001b[94mraise\u001b[0m to_exc(exc) \u001b[94mfrom\u001b[0m \u001b[4;96mexc\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m15 \u001b[0m\u001b[2m│ │ \u001b[0m\u001b[94mraise\u001b[0m \u001b[2m# pragma: nocover\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m16 \u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m17 \u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m╰──────────────────────────────────────────────────────────────────────────────────────────────────╯\u001b[0m\n", + "\u001b[1;91mConnectError: \u001b[0m\u001b[1m[\u001b[0mErrno \u001b[1;36m111\u001b[0m\u001b[1m]\u001b[0m Connection refused\n", + "\n", + "\u001b[3mThe above exception was the direct cause of the following exception:\u001b[0m\n", + "\n", + "\u001b[31m╭─\u001b[0m\u001b[31m──────────────────────────────\u001b[0m\u001b[31m \u001b[0m\u001b[1;31mTraceback \u001b[0m\u001b[1;2;31m(most recent call last)\u001b[0m\u001b[31m \u001b[0m\u001b[31m───────────────────────────────\u001b[0m\u001b[31m─╮\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2;33m/opt/conda/lib/python3.11/site-packages/unitycatalog/\u001b[0m\u001b[1;33m_base_client.py\u001b[0m:\u001b[94m951\u001b[0m in \u001b[92m_request\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 948 \u001b[0m\u001b[2m│ │ \u001b[0mlog.debug(\u001b[33m\"\u001b[0m\u001b[33mSending HTTP Request: \u001b[0m\u001b[33m%s\u001b[0m\u001b[33m \u001b[0m\u001b[33m%s\u001b[0m\u001b[33m\"\u001b[0m, request.method, request.url) \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 949 \u001b[0m\u001b[2m│ │ \u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 950 \u001b[0m\u001b[2m│ │ \u001b[0m\u001b[94mtry\u001b[0m: \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m❱ \u001b[0m 951 \u001b[2m│ │ │ \u001b[0mresponse = \u001b[96mself\u001b[0m._client.send( \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 952 \u001b[0m\u001b[2m│ │ │ │ \u001b[0mrequest, \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 953 \u001b[0m\u001b[2m│ │ │ │ \u001b[0mstream=stream \u001b[95mor\u001b[0m \u001b[96mself\u001b[0m._should_stream_response_body(request=request), \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 954 \u001b[0m\u001b[2m│ │ │ │ \u001b[0m**kwargs, \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2;33m/opt/conda/lib/python3.11/site-packages/httpx/\u001b[0m\u001b[1;33m_client.py\u001b[0m:\u001b[94m914\u001b[0m in \u001b[92msend\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 911 \u001b[0m\u001b[2m│ │ \u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 912 \u001b[0m\u001b[2m│ │ \u001b[0mauth = \u001b[96mself\u001b[0m._build_request_auth(request, auth) \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 913 \u001b[0m\u001b[2m│ │ \u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m❱ \u001b[0m 914 \u001b[2m│ │ \u001b[0mresponse = \u001b[96mself\u001b[0m._send_handling_auth( \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 915 \u001b[0m\u001b[2m│ │ │ \u001b[0mrequest, \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 916 \u001b[0m\u001b[2m│ │ │ \u001b[0mauth=auth, \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 917 \u001b[0m\u001b[2m│ │ │ \u001b[0mfollow_redirects=follow_redirects, \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2;33m/opt/conda/lib/python3.11/site-packages/httpx/\u001b[0m\u001b[1;33m_client.py\u001b[0m:\u001b[94m942\u001b[0m in \u001b[92m_send_handling_auth\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 939 \u001b[0m\u001b[2m│ │ │ \u001b[0mrequest = \u001b[96mnext\u001b[0m(auth_flow) \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 940 \u001b[0m\u001b[2m│ │ │ \u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 941 \u001b[0m\u001b[2m│ │ │ \u001b[0m\u001b[94mwhile\u001b[0m \u001b[94mTrue\u001b[0m: \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m❱ \u001b[0m 942 \u001b[2m│ │ │ │ \u001b[0mresponse = \u001b[96mself\u001b[0m._send_handling_redirects( \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 943 \u001b[0m\u001b[2m│ │ │ │ │ \u001b[0mrequest, \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 944 \u001b[0m\u001b[2m│ │ │ │ │ \u001b[0mfollow_redirects=follow_redirects, \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 945 \u001b[0m\u001b[2m│ │ │ │ │ \u001b[0mhistory=history, \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2;33m/opt/conda/lib/python3.11/site-packages/httpx/\u001b[0m\u001b[1;33m_client.py\u001b[0m:\u001b[94m979\u001b[0m in \u001b[92m_send_handling_redirects\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 976 \u001b[0m\u001b[2m│ │ │ \u001b[0m\u001b[94mfor\u001b[0m hook \u001b[95min\u001b[0m \u001b[96mself\u001b[0m._event_hooks[\u001b[33m\"\u001b[0m\u001b[33mrequest\u001b[0m\u001b[33m\"\u001b[0m]: \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 977 \u001b[0m\u001b[2m│ │ │ │ \u001b[0mhook(request) \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 978 \u001b[0m\u001b[2m│ │ │ \u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m❱ \u001b[0m 979 \u001b[2m│ │ │ \u001b[0mresponse = \u001b[96mself\u001b[0m._send_single_request(request) \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 980 \u001b[0m\u001b[2m│ │ │ \u001b[0m\u001b[94mtry\u001b[0m: \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 981 \u001b[0m\u001b[2m│ │ │ │ \u001b[0m\u001b[94mfor\u001b[0m hook \u001b[95min\u001b[0m \u001b[96mself\u001b[0m._event_hooks[\u001b[33m\"\u001b[0m\u001b[33mresponse\u001b[0m\u001b[33m\"\u001b[0m]: \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 982 \u001b[0m\u001b[2m│ │ │ │ │ \u001b[0mhook(response) \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2;33m/opt/conda/lib/python3.11/site-packages/httpx/\u001b[0m\u001b[1;33m_client.py\u001b[0m:\u001b[94m1015\u001b[0m in \u001b[92m_send_single_request\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m1012 \u001b[0m\u001b[2m│ │ │ \u001b[0m) \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m1013 \u001b[0m\u001b[2m│ │ \u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m1014 \u001b[0m\u001b[2m│ │ \u001b[0m\u001b[94mwith\u001b[0m request_context(request=request): \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m❱ \u001b[0m1015 \u001b[2m│ │ │ \u001b[0mresponse = transport.handle_request(request) \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m1016 \u001b[0m\u001b[2m│ │ \u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m1017 \u001b[0m\u001b[2m│ │ \u001b[0m\u001b[94massert\u001b[0m \u001b[96misinstance\u001b[0m(response.stream, SyncByteStream) \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m1018 \u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2;33m/opt/conda/lib/python3.11/site-packages/httpx/_transports/\u001b[0m\u001b[1;33mdefault.py\u001b[0m:\u001b[94m232\u001b[0m in \u001b[92mhandle_request\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m229 \u001b[0m\u001b[2m│ │ │ \u001b[0mcontent=request.stream, \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m230 \u001b[0m\u001b[2m│ │ │ \u001b[0mextensions=request.extensions, \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m231 \u001b[0m\u001b[2m│ │ \u001b[0m) \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m❱ \u001b[0m232 \u001b[2m│ │ \u001b[0m\u001b[94mwith\u001b[0m map_httpcore_exceptions(): \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m233 \u001b[0m\u001b[2m│ │ │ \u001b[0mresp = \u001b[96mself\u001b[0m._pool.handle_request(req) \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m234 \u001b[0m\u001b[2m│ │ \u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m235 \u001b[0m\u001b[2m│ │ \u001b[0m\u001b[94massert\u001b[0m \u001b[96misinstance\u001b[0m(resp.stream, typing.Iterable) \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2;33m/opt/conda/lib/python3.11/\u001b[0m\u001b[1;33mcontextlib.py\u001b[0m:\u001b[94m158\u001b[0m in \u001b[92m__exit__\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m155 \u001b[0m\u001b[2m│ │ │ │ \u001b[0m\u001b[2m# tell if we get the same exception back\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m156 \u001b[0m\u001b[2m│ │ │ │ \u001b[0mvalue = typ() \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m157 \u001b[0m\u001b[2m│ │ │ \u001b[0m\u001b[94mtry\u001b[0m: \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m❱ \u001b[0m158 \u001b[2m│ │ │ │ \u001b[0m\u001b[96mself\u001b[0m.gen.throw(typ, value, traceback) \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m159 \u001b[0m\u001b[2m│ │ │ \u001b[0m\u001b[94mexcept\u001b[0m \u001b[96mStopIteration\u001b[0m \u001b[94mas\u001b[0m exc: \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m160 \u001b[0m\u001b[2m│ │ │ │ \u001b[0m\u001b[2m# Suppress StopIteration *unless* it's the same exception that\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m161 \u001b[0m\u001b[2m│ │ │ │ \u001b[0m\u001b[2m# was passed to throw(). This prevents a StopIteration\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2;33m/opt/conda/lib/python3.11/site-packages/httpx/_transports/\u001b[0m\u001b[1;33mdefault.py\u001b[0m:\u001b[94m86\u001b[0m in \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[92mmap_httpcore_exceptions\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 83 \u001b[0m\u001b[2m│ │ │ \u001b[0m\u001b[94mraise\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 84 \u001b[0m\u001b[2m│ │ \u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 85 \u001b[0m\u001b[2m│ │ \u001b[0mmessage = \u001b[96mstr\u001b[0m(exc) \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m❱ \u001b[0m 86 \u001b[2m│ │ \u001b[0m\u001b[94mraise\u001b[0m mapped_exc(message) \u001b[94mfrom\u001b[0m \u001b[4;96mexc\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 87 \u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 88 \u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 89 \u001b[0mHTTPCORE_EXC_MAP = { \u001b[31m│\u001b[0m\n", + "\u001b[31m╰──────────────────────────────────────────────────────────────────────────────────────────────────╯\u001b[0m\n", + "\u001b[1;91mConnectError: \u001b[0m\u001b[1m[\u001b[0mErrno \u001b[1;36m111\u001b[0m\u001b[1m]\u001b[0m Connection refused\n", + "\n", + "\u001b[3mThe above exception was the direct cause of the following exception:\u001b[0m\n", + "\n", + "\u001b[31m╭─\u001b[0m\u001b[31m──────────────────────────────\u001b[0m\u001b[31m \u001b[0m\u001b[1;31mTraceback \u001b[0m\u001b[1;2;31m(most recent call last)\u001b[0m\u001b[31m \u001b[0m\u001b[31m───────────────────────────────\u001b[0m\u001b[31m─╮\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2;33m/home/jovyan/workspaces/\u001b[0m\u001b[1;33muc_oss_catalog_viewer.py\u001b[0m:\u001b[94m147\u001b[0m in \u001b[92mdisplay_catalog\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m144 \u001b[0m\u001b[2m│ \u001b[0m\u001b[94mtry\u001b[0m: \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m145 \u001b[0m\u001b[2m│ │ \u001b[0mconsole = Console(width=\u001b[94m200\u001b[0m) \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m146 \u001b[0m\u001b[2m│ │ \u001b[0m\u001b[94mwith\u001b[0m console.pager(): \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m❱ \u001b[0m147 \u001b[2m│ │ │ \u001b[0mconsole.print(generate_table()) \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m148 \u001b[0m\u001b[2m│ \u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m149 \u001b[0m\u001b[2m│ \u001b[0m\u001b[94mexcept\u001b[0m \u001b[96mException\u001b[0m \u001b[94mas\u001b[0m e: \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m150 \u001b[0m\u001b[2m│ │ \u001b[0m\u001b[96mprint\u001b[0m() \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2;33m/home/jovyan/workspaces/\u001b[0m\u001b[1;33muc_oss_catalog_viewer.py\u001b[0m:\u001b[94m49\u001b[0m in \u001b[92mgenerate_table\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 46 \u001b[0m\u001b[2m│ │ \u001b[0mtable.add_column(\u001b[33m\"\u001b[0m\u001b[33mVol/Tab\u001b[0m\u001b[33m\"\u001b[0m) \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 47 \u001b[0m\u001b[2m│ │ \u001b[0mtable.add_column(\u001b[33m\"\u001b[0m\u001b[33mDetails\u001b[0m\u001b[33m\"\u001b[0m) \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 48 \u001b[0m\u001b[2m│ │ \u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m❱ \u001b[0m 49 \u001b[2m│ │ \u001b[0mc_list = uc_client.catalogs.list() \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 50 \u001b[0m\u001b[2m│ │ \u001b[0mcatalogs= c_list.catalogs \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 51 \u001b[0m\u001b[2m│ │ \u001b[0m\u001b[94mfor\u001b[0m catalog \u001b[95min\u001b[0m catalogs: \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 52 \u001b[0m\u001b[2m│ │ │ \u001b[0m\u001b[2m# cat_info = catalog.CatalogInfo\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2;33m/opt/conda/lib/python3.11/site-packages/unitycatalog/resources/\u001b[0m\u001b[1;33mcatalogs.py\u001b[0m:\u001b[94m211\u001b[0m in \u001b[92mlist\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m208 \u001b[0m\u001b[2m│ │ \u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m209 \u001b[0m\u001b[2;33m│ │ \u001b[0m\u001b[33mtimeout: Override the client-level default timeout for this request, in second\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m210 \u001b[0m\u001b[2;33m│ │ \u001b[0m\u001b[33m\"\"\"\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m❱ \u001b[0m211 \u001b[2m│ │ \u001b[0m\u001b[94mreturn\u001b[0m \u001b[96mself\u001b[0m._get( \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m212 \u001b[0m\u001b[2m│ │ │ \u001b[0m\u001b[33m\"\u001b[0m\u001b[33m/catalogs\u001b[0m\u001b[33m\"\u001b[0m, \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m213 \u001b[0m\u001b[2m│ │ │ \u001b[0moptions=make_request_options( \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m214 \u001b[0m\u001b[2m│ │ │ │ \u001b[0mextra_headers=extra_headers, \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2;33m/opt/conda/lib/python3.11/site-packages/unitycatalog/\u001b[0m\u001b[1;33m_base_client.py\u001b[0m:\u001b[94m1168\u001b[0m in \u001b[92mget\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m1165 \u001b[0m\u001b[2m│ │ \u001b[0mopts = FinalRequestOptions.construct(method=\u001b[33m\"\u001b[0m\u001b[33mget\u001b[0m\u001b[33m\"\u001b[0m, url=path, **options) \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m1166 \u001b[0m\u001b[2m│ │ \u001b[0m\u001b[2m# cast is required because mypy complains about returning Any even though\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m1167 \u001b[0m\u001b[2m│ │ \u001b[0m\u001b[2m# it understands the type variables\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m❱ \u001b[0m1168 \u001b[2m│ │ \u001b[0m\u001b[94mreturn\u001b[0m cast(ResponseT, \u001b[96mself\u001b[0m.request(cast_to, opts, stream=stream, stream_cls=str \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m1169 \u001b[0m\u001b[2m│ \u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m1170 \u001b[0m\u001b[2m│ \u001b[0m\u001b[1;95m@overload\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m1171 \u001b[0m\u001b[2m│ \u001b[0m\u001b[94mdef\u001b[0m \u001b[92mpost\u001b[0m( \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2;33m/opt/conda/lib/python3.11/site-packages/unitycatalog/\u001b[0m\u001b[1;33m_base_client.py\u001b[0m:\u001b[94m920\u001b[0m in \u001b[92mrequest\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 917 \u001b[0m\u001b[2m│ │ \u001b[0mstream: \u001b[96mbool\u001b[0m = \u001b[94mFalse\u001b[0m, \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 918 \u001b[0m\u001b[2m│ │ \u001b[0mstream_cls: \u001b[96mtype\u001b[0m[_StreamT] | \u001b[94mNone\u001b[0m = \u001b[94mNone\u001b[0m, \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 919 \u001b[0m\u001b[2m│ \u001b[0m) -> ResponseT | _StreamT: \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m❱ \u001b[0m 920 \u001b[2m│ │ \u001b[0m\u001b[94mreturn\u001b[0m \u001b[96mself\u001b[0m._request( \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 921 \u001b[0m\u001b[2m│ │ │ \u001b[0mcast_to=cast_to, \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 922 \u001b[0m\u001b[2m│ │ │ \u001b[0moptions=options, \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 923 \u001b[0m\u001b[2m│ │ │ \u001b[0mstream=stream, \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2;33m/opt/conda/lib/python3.11/site-packages/unitycatalog/\u001b[0m\u001b[1;33m_base_client.py\u001b[0m:\u001b[94m975\u001b[0m in \u001b[92m_request\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 972 \u001b[0m\u001b[2m│ │ │ \u001b[0mlog.debug(\u001b[33m\"\u001b[0m\u001b[33mEncountered Exception\u001b[0m\u001b[33m\"\u001b[0m, exc_info=\u001b[94mTrue\u001b[0m) \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 973 \u001b[0m\u001b[2m│ │ │ \u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 974 \u001b[0m\u001b[2m│ │ │ \u001b[0m\u001b[94mif\u001b[0m retries > \u001b[94m0\u001b[0m: \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m❱ \u001b[0m 975 \u001b[2m│ │ │ │ \u001b[0m\u001b[94mreturn\u001b[0m \u001b[96mself\u001b[0m._retry_request( \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 976 \u001b[0m\u001b[2m│ │ │ │ │ \u001b[0moptions, \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 977 \u001b[0m\u001b[2m│ │ │ │ │ \u001b[0mcast_to, \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 978 \u001b[0m\u001b[2m│ │ │ │ │ \u001b[0mretries, \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2;33m/opt/conda/lib/python3.11/site-packages/unitycatalog/\u001b[0m\u001b[1;33m_base_client.py\u001b[0m:\u001b[94m1051\u001b[0m in \u001b[92m_retry_request\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m1048 \u001b[0m\u001b[2m│ │ \u001b[0m\u001b[2m# different thread if necessary.\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m1049 \u001b[0m\u001b[2m│ │ \u001b[0mtime.sleep(timeout) \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m1050 \u001b[0m\u001b[2m│ │ \u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m❱ \u001b[0m1051 \u001b[2m│ │ \u001b[0m\u001b[94mreturn\u001b[0m \u001b[96mself\u001b[0m._request( \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m1052 \u001b[0m\u001b[2m│ │ │ \u001b[0moptions=options, \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m1053 \u001b[0m\u001b[2m│ │ │ \u001b[0mcast_to=cast_to, \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m1054 \u001b[0m\u001b[2m│ │ │ \u001b[0mremaining_retries=remaining, \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2;33m/opt/conda/lib/python3.11/site-packages/unitycatalog/\u001b[0m\u001b[1;33m_base_client.py\u001b[0m:\u001b[94m975\u001b[0m in \u001b[92m_request\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 972 \u001b[0m\u001b[2m│ │ │ \u001b[0mlog.debug(\u001b[33m\"\u001b[0m\u001b[33mEncountered Exception\u001b[0m\u001b[33m\"\u001b[0m, exc_info=\u001b[94mTrue\u001b[0m) \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 973 \u001b[0m\u001b[2m│ │ │ \u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 974 \u001b[0m\u001b[2m│ │ │ \u001b[0m\u001b[94mif\u001b[0m retries > \u001b[94m0\u001b[0m: \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m❱ \u001b[0m 975 \u001b[2m│ │ │ │ \u001b[0m\u001b[94mreturn\u001b[0m \u001b[96mself\u001b[0m._retry_request( \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 976 \u001b[0m\u001b[2m│ │ │ │ │ \u001b[0moptions, \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 977 \u001b[0m\u001b[2m│ │ │ │ │ \u001b[0mcast_to, \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 978 \u001b[0m\u001b[2m│ │ │ │ │ \u001b[0mretries, \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2;33m/opt/conda/lib/python3.11/site-packages/unitycatalog/\u001b[0m\u001b[1;33m_base_client.py\u001b[0m:\u001b[94m1051\u001b[0m in \u001b[92m_retry_request\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m1048 \u001b[0m\u001b[2m│ │ \u001b[0m\u001b[2m# different thread if necessary.\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m1049 \u001b[0m\u001b[2m│ │ \u001b[0mtime.sleep(timeout) \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m1050 \u001b[0m\u001b[2m│ │ \u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m❱ \u001b[0m1051 \u001b[2m│ │ \u001b[0m\u001b[94mreturn\u001b[0m \u001b[96mself\u001b[0m._request( \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m1052 \u001b[0m\u001b[2m│ │ │ \u001b[0moptions=options, \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m1053 \u001b[0m\u001b[2m│ │ │ \u001b[0mcast_to=cast_to, \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m1054 \u001b[0m\u001b[2m│ │ │ \u001b[0mremaining_retries=remaining, \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2;33m/opt/conda/lib/python3.11/site-packages/unitycatalog/\u001b[0m\u001b[1;33m_base_client.py\u001b[0m:\u001b[94m985\u001b[0m in \u001b[92m_request\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 982 \u001b[0m\u001b[2m│ │ │ │ \u001b[0m) \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 983 \u001b[0m\u001b[2m│ │ │ \u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 984 \u001b[0m\u001b[2m│ │ │ \u001b[0mlog.debug(\u001b[33m\"\u001b[0m\u001b[33mRaising connection error\u001b[0m\u001b[33m\"\u001b[0m) \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m❱ \u001b[0m 985 \u001b[2m│ │ │ \u001b[0m\u001b[94mraise\u001b[0m APIConnectionError(request=request) \u001b[94mfrom\u001b[0m \u001b[4;96merr\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 986 \u001b[0m\u001b[2m│ │ \u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 987 \u001b[0m\u001b[2m│ │ \u001b[0mlog.debug( \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 988 \u001b[0m\u001b[2m│ │ │ \u001b[0m\u001b[33m'\u001b[0m\u001b[33mHTTP Response: \u001b[0m\u001b[33m%s\u001b[0m\u001b[33m \u001b[0m\u001b[33m%s\u001b[0m\u001b[33m \u001b[0m\u001b[33m\"\u001b[0m\u001b[33m%i\u001b[0m\u001b[33m \u001b[0m\u001b[33m%s\u001b[0m\u001b[33m\"\u001b[0m\u001b[33m \u001b[0m\u001b[33m%s\u001b[0m\u001b[33m'\u001b[0m, \u001b[31m│\u001b[0m\n", + "\u001b[31m╰──────────────────────────────────────────────────────────────────────────────────────────────────╯\u001b[0m\n", + "\u001b[1;91mAPIConnectionError: \u001b[0mConnection error.\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "%run uc_oss_catalog_viewer.py\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4885e0e1-2a71-420b-b3d3-257e4eb321e3", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.9" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/workspaces/starter.jupyterlab-workspace b/workspaces/starter.jupyterlab-workspace new file mode 100644 index 0000000..3211591 --- /dev/null +++ b/workspaces/starter.jupyterlab-workspace @@ -0,0 +1 @@ +{"data":{"layout-restorer:data":{"main":{"dock":{"type":"split-area","orientation":"vertical","sizes":[0.5,0.5],"children":[{"type":"tab-area","currentIndex":1,"widgets":["terminal:1","terminal:2"]},{"type":"tab-area","currentIndex":0,"widgets":["notebook:workspaces/Untitled1.ipynb"]}]},"current":"terminal:2"},"down":{"size":0,"widgets":[]},"left":{"collapsed":false,"visible":true,"current":"filebrowser","widgets":["filebrowser","running-sessions","git-sessions","@jupyterlab/toc:plugin","extensionmanager.main-view"],"widgetStates":{"jp-running-sessions":{"sizes":[0.16666666666666666,0.16666666666666666,0.16666666666666666,0.16666666666666666,0.16666666666666666,0.16666666666666666],"expansionStates":[false,false,false,false,false,false]},"extensionmanager.main-view":{"sizes":[0.3333333333333333,0.3333333333333333,0.3333333333333333],"expansionStates":[false,false,false]}}},"right":{"collapsed":true,"visible":true,"widgets":["jp-property-inspector","debugger-sidebar"],"widgetStates":{"jp-debugger-sidebar":{"sizes":[0.2,0.2,0.2,0.2,0.2],"expansionStates":[false,false,false,false,false]}}},"relativeSizes":[0.15991077119184194,0.840089228808158,0],"top":{"simpleVisibility":true}},"docmanager:recents":{"opened":[{"path":"workspaces","contentType":"directory","root":"/home/jovyan"},{"path":"workspaces/Untitled1.ipynb","contentType":"notebook","factory":"Notebook","root":"/home/jovyan"}],"closed":[]},"file-browser-filebrowser:cwd":{"path":"workspaces"},"notebook:workspaces/Untitled1.ipynb":{"data":{"path":"workspaces/Untitled1.ipynb","factory":"Notebook"}},"terminal:1":{"data":{"name":"1"}},"terminal:2":{"data":{"name":"2"}}},"metadata":{"id":"starter","last_modified":"2024-07-17T21:18:11.194040+00:00","created":"2024-07-17T21:18:11.194040+00:00"}} \ No newline at end of file diff --git a/workspaces/uc_oss_catalog_viewer.py b/workspaces/uc_oss_catalog_viewer.py new file mode 100644 index 0000000..d61e7e6 --- /dev/null +++ b/workspaces/uc_oss_catalog_viewer.py @@ -0,0 +1,167 @@ +#!/usr/bin/env python3 +""" +SPDX-License-Identifier: Apache-2.0 +Copyright Contributors to the ODPi Egeria project. + +A simple UC catalog viewer using the RICH textual interface. + + +""" +import argparse +import time + +from rich import box +from rich.console import Console +from rich.markdown import Markdown +from rich.table import Table +from rich.text import Text +from rich.live import Live + + +from unitycatalog import Unitycatalog +from unitycatalog.types import (catalog_info, catalog_list_response, + schema_info, schema_list_response, + volume_info, volume_list_response, + ) + + +def display_catalog(url:str = "http://egeria.pdr-associates.com:8070"): + base_url = url + "/api/2.1/unity-catalog" + uc_client = Unitycatalog(base_url=base_url) + + def generate_table() -> Table: + """Make a new table.""" + table = Table( + title=f"UC OSS Catalog @ {time.asctime()}", + header_style="white on dark_blue", + show_lines=True, + box=box.ROUNDED, + caption=f"Running on {url}", + expand=True + ) + table.add_column("Catalog Name") + table.add_column("Catalog Comment") + table.add_column("Schema Name") + table.add_column("Schema Comment") + table.add_column("Vol/Tab") + table.add_column("Details") + + c_list = uc_client.catalogs.list() + catalogs= c_list.catalogs + for catalog in catalogs: + # cat_info = catalog.CatalogInfo + cat_name = catalog.name + cat_comment = catalog.comment + + s_list = uc_client.schemas.list(catalog_name = cat_name) + schemas = s_list.schemas + for schema in schemas: + schema_name = schema.name + schema_comment = schema.comment + v_list = uc_client.volumes.list(catalog_name = cat_name, schema_name = schema_name) + volumes = v_list.volumes + volume_md = " " + volumes_m = "Volumes" + volume_m = " " + tab_list = uc_client.tables.list(catalog_name = cat_name, schema_name = schema_name) + tables = tab_list.tables + tables_m = "Tables" + + func_list = uc_client.functions.list(catalog_name = cat_name, schema_name = schema_name) + functions = func_list.functions + if (functions is not None) and (len(functions) > 0): + function_m = "Functions" + for function in functions: + function_name = function.name + function_comment = function.comment + function_sql = function.sql_data_access + function_id = function.function_id + function_type = function.full_data_type + function_m = (f"{function_m}\n* name: {function_name}\n" + f"\t* comment: {function_comment}\n" + f"\t* function_id: {function_id}\n" + f"\t* function_type: {function_type}\n" + f"\t* sql_access: {function_sql}\n") + table.add_row(cat_name, cat_comment, schema_name, schema_comment, Text(function_m)) + if (volumes is not None) and (len(volumes) > 0): + for volume in volumes: + volume_name = volume.name + volume_comment = volume.comment + volume_created_at = volume.created_at + volume_updated_at = volume.updated_at + volume_id = volume.volume_id + volume_type = volume.volume_type + volume_storage_location = volume.storage_location + volume_m = (f"{volume_m}\n* name: {volume_name}\n" + f"\t* comment: {volume_comment}\n" + f"\t* created_at: {volume_created_at}\n" + f"\t* updated_at: {volume_updated_at}\n" + f"\t* volume_id: {volume_id}\n" + f"\t* volume_type: {volume_type}\n" + f"\t* storage_location: {volume_storage_location}\n") + volumes_m = f"{volumes_m}\n{volume_m}" + table.add_row(cat_name, cat_comment, schema_name, schema_comment, Text(volumes_m)) + tab_m = " " + if (tables is not None) and (len(tables) > 0): + for tab in tables: + tab_name = tab.name + tab_comment = tab.comment + tab_created_at = tab.created_at + + tab_src_format = tab.data_source_format + tab_id = tab.table_id + tab_type = tab.table_type + tab_updated_at = tab.updated_at + columns = tab.columns + col_m = " " + col_tab = Table() + col_tab.add_column("name") + col_tab.add_column("type") + col_tab.add_column("nullable") + col_tab.add_column("comment") + + for col in columns: + col_nullable = "true" if col.nullable else "false" + col_tab.add_row(col.name, col.type_text, col_nullable, col.comment) + + table_m = (f"{tab_m}\n* name: {tab_name}\n" + f"\t* comment: {tab_comment}\n" + f"\t* created_at: {tab_created_at}\n" + f"\t* updated_at: {tab_updated_at}\n" + f"\t* table_id: {tab_id}\n" + f"\t* table_type: {tab_type}\n" + f"\t* src_format: {tab_src_format}\n" + ) + + tables_m = f"{tables_m}\n{table_m}" + table.add_row(cat_name, cat_comment, schema_name, schema_comment, Text(tables_m), col_tab) + fcn_m = " " + + + + return table + + try: + console = Console(width=200) + with console.pager(): + console.print(generate_table()) + + except Exception as e: + print() + console.print_exception() + + finally: + uc_client.close() + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("--server", help="Name of the server to display status for") + parser.add_argument("--url", help="URL Platform to connect to") + parser.add_argument("--userid", help="User Id") + args = parser.parse_args() + + server = args.server if args.server is not None else "engine-host" + url = args.url if args.url is not None else "http://localhost:8080" + userid = args.userid if args.userid is not None else 'garygeeke' + display_catalog(url)