diff --git a/.bazelrc b/.bazelrc index 266fbd38..2777f4cf 100644 --- a/.bazelrc +++ b/.bazelrc @@ -1,3 +1,22 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + build --incompatible_strict_action_env run --incompatible_strict_action_env test --incompatible_strict_action_env diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index efe57ebc..00000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,295 +0,0 @@ -# -# Copyright (C) 2020 Grakn Labs -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# - -version: 2.1 -commands: - install-bazel: - steps: - - run: curl -OL https://raw.githubusercontent.com/graknlabs/dependencies/master/tool/bazelinstall/linux.sh - - run: bash ./linux.sh && rm ./linux.sh - - run: curl -OL https://raw.githubusercontent.com/graknlabs/dependencies/master/tool/bazelinstall/rbe.sh - - run: bash ./rbe.sh && rm ./rbe.sh - - run-bazel: - parameters: - command: - type: string - steps: - - run: bazel run @graknlabs_dependencies//tool/bazelrun:rbe -- << parameters.command >> - - install-artifact-credentials: - steps: - - run: | - ARTIFACT_USERNAME=$REPO_GRAKN_USERNAME \ - ARTIFACT_PASSWORD=$REPO_GRAKN_PASSWORD \ - bazel run @graknlabs_dependencies//distribution/artifact:create-netrc - - run-grakn: - steps: - - run-bazel: - command: bazel run //:grakn-extractor -- dist/grakn-core-server-linux - - run: nohup ./dist/grakn-core-server-linux/grakn server start - -jobs: - build: - machine: - image: ubuntu-1604:201903-01 - working_directory: ~/client-python - steps: - - checkout - - install-bazel - - install-artifact-credentials - - run-bazel: - command: bazel build //... - - test-concept: - machine: - image: ubuntu-1604:201903-01 - working_directory: ~/client-python - steps: - - checkout - - install-bazel - - install-artifact-credentials - - run-bazel: - command: bazel test //:test_concept --test_output=errors - - test-grakn: - machine: - image: ubuntu-1604:201903-01 - working_directory: ~/client-python - steps: - - checkout - - install-bazel - - install-artifact-credentials - - run-bazel: - command: bazel test //:test_grakn --test_output=errors - - test-keyspace: - machine: - image: ubuntu-1604:201903-01 - working_directory: ~/client-python - steps: - - checkout - - install-bazel - - install-artifact-credentials - - run-bazel: - command: bazel test //:test_keyspace --test_output=errors - - test-answer: - machine: - image: ubuntu-1604:201903-01 - working_directory: ~/client-python - steps: - - checkout - - install-bazel - - install-artifact-credentials - - run-bazel: - command: bazel test //:test_answer --test_output=errors - - deploy-pip-snapshot: - machine: - image: ubuntu-1604:201903-01 - working_directory: ~/client-python - steps: - - checkout - - install-bazel - - run: | - export DEPLOY_PIP_USERNAME=$REPO_GRAKN_USERNAME - export DEPLOY_PIP_PASSWORD=$REPO_GRAKN_PASSWORD - bazel run --define version=$(git rev-parse HEAD) //:deploy-pip -- snapshot - - test-deployment: - machine: - image: ubuntu-1604:201903-01 - working_directory: ~/client-python - steps: - - checkout - - install-bazel - - install-artifact-credentials - - run: sudo apt-get update - - run: sudo apt-get install python-pip - - run-grakn - - run: sleep 60 - - run: - name: Run test-deployment for client-python - command: | - echo -n "0.0.0-$CIRCLE_SHA1" > VERSION - sed -i -e "s/CLIENT_PYTHON_VERSION_MARKER/$(cat VERSION)/g" tests/deployment/requirements.txt - cat tests/deployment/requirements.txt - pip install --upgrade pip - pip install -r tests/deployment/requirements.txt - cd tests/deployment/ && python -m unittest test - - sync-dependencies-snapshot: - machine: - image: ubuntu-1604:201903-01 - working_directory: ~/client-python - steps: - - install-bazel - - checkout - - run: | - export SYNC_DEPENDENCIES_TOKEN=$REPO_GITHUB_TOKEN - bazel run @graknlabs_dependencies//tool/sync:dependencies -- \ - --source client-python@$CIRCLE_SHA1 \ - --targets grakn-kgms:master kglib:master - - release-approval: - machine: - image: ubuntu-1604:201903-01 - working_directory: ~/client-python - steps: - - install-bazel - - checkout - - run: | - export RELEASE_APPROVAL_USERNAME=$REPO_GITHUB_USERNAME - export RELEASE_APPROVAL_TOKEN=$REPO_GITHUB_TOKEN - bazel run @graknlabs_dependencies//tool/release:approval - - release-validate: - machine: - image: ubuntu-1604:201903-01 - steps: - - install-bazel - - checkout - - run: | - bazel test //:release-validate-deps --test_output=streamed - - deploy-github: - machine: - image: ubuntu-1604:201903-01 - working_directory: ~/client-python - steps: - - install-bazel - - checkout - - run: | - pip install certifi - export RELEASE_NOTES_TOKEN=$REPO_GITHUB_TOKEN - bazel run @graknlabs_dependencies//tool/release:create-notes -- client-python $(cat VERSION) ./RELEASE_TEMPLATE.md - - run: bazel clean --expunge - - run: | - export DEPLOY_GITHUB_TOKEN=$REPO_GITHUB_TOKEN - bazel run --define version=$(cat VERSION) //:deploy-github -- $CIRCLE_SHA1 - - deploy-pip-release: - machine: - image: ubuntu-1604:201903-01 - working_directory: ~/client-python - steps: - - install-bazel - - checkout - - run: | - export DEPLOY_PIP_USERNAME=$REPO_PYPI_USERNAME - export DEPLOY_PIP_PASSWORD=$REPO_PYPI_PASSWORD - bazel run --define version=$(cat VERSION) //:deploy-pip -- release - - sync-dependencies-release: - machine: - image: ubuntu-1604:201903-01 - working_directory: ~/client-python - steps: - - install-bazel - - checkout - - run: | - export SYNC_DEPENDENCIES_TOKEN=$REPO_GITHUB_TOKEN - bazel run @graknlabs_dependencies//tool/sync:dependencies -- \ - --source client-python@$(cat VERSION) \ - --targets grakn-kgms:master docs:master examples:master kglib:master - - release-cleanup: - machine: - image: ubuntu-1604:201903-01 - steps: - - checkout - - run: git push --delete https://$REPO_GITHUB_TOKEN@github.com/graknlabs/client-python $CIRCLE_BRANCH - -workflows: - client-python: - jobs: - - build: - filters: - branches: - ignore: client-python-release-branch - - test-concept: - filters: - branches: - ignore: client-python-release-branch - - test-grakn: - filters: - branches: - ignore: client-python-release-branch - - test-keyspace: - filters: - branches: - ignore: client-python-release-branch - - test-answer: - filters: - branches: - ignore: client-python-release-branch - - deploy-pip-snapshot: - filters: - branches: - only: master - requires: - - build - - test-concept - - test-grakn - - test-keyspace - - test-answer - - test-deployment: - filters: - branches: - only: master - requires: - - deploy-pip-snapshot - - release-approval: - filters: - branches: - only: master - requires: - - test-deployment - - client-python-release: - jobs: - - release-validate: - filters: - branches: - only: client-python-release-branch - - deploy-github: - filters: - branches: - only: client-python-release-branch - requires: - - release-validate - - deploy-approval: - type: approval - filters: - branches: - only: client-python-release-branch - requires: - - deploy-github - - deploy-pip-release: - filters: - branches: - only: client-python-release-branch - requires: - - deploy-approval - - release-cleanup: - filters: - branches: - only: client-python-release-branch - requires: - - deploy-pip-release \ No newline at end of file diff --git a/.gitignore b/.gitignore index 4f666c5f..7b8b1728 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,84 @@ -.ijwb -.idea +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# Mac files # +.DS_Store + +# Bazel files # bazel-* + +# IDE files # +.idea/ +*.iml +*.geany +.ijwb/ + +# VS Code files # +.vscode/ +.settings/ +.project +.classpath + +# Compiled files # +dist/ +out/ +*.class + +# Mobile Tools for Java (J2ME) files # +.mtj.tmp/ + +# Package Files # +*.jarg +*.war +*.ear + +# Debug files # +dep.tree + +# Databases # +db/ + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml # +hs_err_pid* + +# Log files # +logs/ +*.log + +# Analytics files # +grakn-test/db/ +grakn-test/output/ +grakn-test/*.txt + +# Sample Date Files # +grakn-test/test-biomed/data/* + +# Benchmark Files # +grakn-test/test-integration/benchmarks/ + +# VIM swap files # +*.swp +*.swo + +# Python cache # __pycache__ + +# Other # **.pyc -venv \ No newline at end of file +venv diff --git a/.grabl/automation.yml b/.grabl/automation.yml index 515c82a5..b32a34d9 100644 --- a/.grabl/automation.yml +++ b/.grabl/automation.yml @@ -1,7 +1,37 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + config: version-candidate: VERSION + dependencies: + dependencies: [build] + common: [build, release] build: + quality: + filter: + owner: graknlabs + branch: master + dependency-analysis: + machine: graknlabs-ubuntu-20.04 + script: | + bazel run @graknlabs_dependencies//grabl/analysis:dependency-analysis correctness: build: machine: graknlabs-ubuntu-20.04 @@ -15,47 +45,51 @@ build: ARTIFACT_PASSWORD=$REPO_GRAKN_PASSWORD \ bazel run @graknlabs_dependencies//distribution/artifact:create-netrc bazel build //... - test-concept: - machine: graknlabs-ubuntu-20.04 - type: foreground - script: | - pyenv global 3.6.10 - sudo unlink /usr/bin/python3 - sudo ln -s $(which python3) /usr/bin/python3 - sudo ln -s /usr/share/pyshared/lsb_release.py /opt/pyenv/versions/3.6.10/lib/python3.6/site-packages/lsb_release.py - ARTIFACT_USERNAME=$REPO_GRAKN_USERNAME \ - ARTIFACT_PASSWORD=$REPO_GRAKN_PASSWORD \ - bazel run @graknlabs_dependencies//distribution/artifact:create-netrc - bazel test //:test_concept --test_output=errors - test-keyspace: - machine: graknlabs-ubuntu-20.04 - type: foreground - script: | - pyenv global 3.6.10 - sudo unlink /usr/bin/python3 - sudo ln -s $(which python3) /usr/bin/python3 - sudo ln -s /usr/share/pyshared/lsb_release.py /opt/pyenv/versions/3.6.10/lib/python3.6/site-packages/lsb_release.py - ARTIFACT_USERNAME=$REPO_GRAKN_USERNAME \ - ARTIFACT_PASSWORD=$REPO_GRAKN_PASSWORD \ - bazel run @graknlabs_dependencies//distribution/artifact:create-netrc - bazel test //:test_keyspace --test_output=errors - test-answer: - machine: graknlabs-ubuntu-20.04 - type: foreground - script: | - pyenv global 3.6.10 - sudo unlink /usr/bin/python3 - sudo ln -s $(which python3) /usr/bin/python3 - sudo ln -s /usr/share/pyshared/lsb_release.py /opt/pyenv/versions/3.6.10/lib/python3.6/site-packages/lsb_release.py - ARTIFACT_USERNAME=$REPO_GRAKN_USERNAME \ - ARTIFACT_PASSWORD=$REPO_GRAKN_PASSWORD \ - bazel run @graknlabs_dependencies//distribution/artifact:create-netrc - bazel test //:test_answer --test_output=errors + bazel run @graknlabs_dependencies//tool/checkstyle:test-coverage + bazel test $(bazel query 'kind(checkstyle_test, //...)') +# test-concept: +# machine: graknlabs-ubuntu-20.04 +# type: foreground +# script: | +# pyenv global 3.6.10 +# sudo unlink /usr/bin/python3 +# sudo ln -s $(which python3) /usr/bin/python3 +# sudo ln -s /usr/share/pyshared/lsb_release.py /opt/pyenv/versions/3.6.10/lib/python3.6/site-packages/lsb_release.py +# ARTIFACT_USERNAME=$REPO_GRAKN_USERNAME \ +# ARTIFACT_PASSWORD=$REPO_GRAKN_PASSWORD \ +# bazel run @graknlabs_dependencies//distribution/artifact:create-netrc +# bazel test //:test_concept --test_output=errors +# test-connection: +# machine: graknlabs-ubuntu-20.04 +# type: foreground +# script: | +# pyenv global 3.6.10 +# sudo unlink /usr/bin/python3 +# sudo ln -s $(which python3) /usr/bin/python3 +# sudo ln -s /usr/share/pyshared/lsb_release.py /opt/pyenv/versions/3.6.10/lib/python3.6/site-packages/lsb_release.py +# ARTIFACT_USERNAME=$REPO_GRAKN_USERNAME \ +# ARTIFACT_PASSWORD=$REPO_GRAKN_PASSWORD \ +# bazel run @graknlabs_dependencies//distribution/artifact:create-netrc +# bazel test //:test_connection --test_output=errors +# test-query: +# machine: graknlabs-ubuntu-20.04 +# type: foreground +# script: | +# pyenv global 3.6.10 +# sudo unlink /usr/bin/python3 +# sudo ln -s $(which python3) /usr/bin/python3 +# sudo ln -s /usr/share/pyshared/lsb_release.py /opt/pyenv/versions/3.6.10/lib/python3.6/site-packages/lsb_release.py +# ARTIFACT_USERNAME=$REPO_GRAKN_USERNAME \ +# ARTIFACT_PASSWORD=$REPO_GRAKN_PASSWORD \ +# bazel run @graknlabs_dependencies//distribution/artifact:create-netrc +# bazel test //:test_query --test_output=errors deploy-pip-snapshot: + machine: graknlabs-ubuntu-20.04 + # TODO: should depend on tests + dependencies: [build] filter: owner: graknlabs branch: master - machine: graknlabs-ubuntu-20.04 type: foreground script: | pyenv global 3.6.10 @@ -69,12 +103,12 @@ build: export DEPLOY_PIP_PASSWORD=$REPO_GRAKN_PASSWORD bazel run --define version=$(git rev-parse HEAD) //:deploy-pip -- snapshot test-deployment: + machine: graknlabs-ubuntu-20.04 + dependencies: [deploy-pip-snapshot] filter: owner: graknlabs branch: master - machine: graknlabs-ubuntu-20.04 type: foreground - dependencies: [deploy-pip-snapshot] script: | pyenv global 3.6.10 sudo unlink /usr/bin/python3 @@ -86,29 +120,22 @@ build: bazel run //:grakn-extractor -- dist/grakn-core-server-linux nohup ./dist/grakn-core-server-linux/grakn server start sleep 60 - echo -n "0.0.0-$CIRCLE_SHA1" > VERSION - sed -i -e "s/CLIENT_PYTHON_VERSION_MARKER/$(cat VERSION)/g" tests/deployment/requirements.txt - cat tests/deployment/requirements.txt + echo -n "0.0.0-$GRABL_COMMIT" > VERSION + sed -i -e "s/CLIENT_PYTHON_VERSION_MARKER/$(cat VERSION)/g" test/deployment/requirements.txt + cat test/deployment/requirements.txt pip install --upgrade pip - pip install -r tests/deployment/requirements.txt - cd tests/deployment/ && python -m unittest test - execution: - - build - - test-concept - - test-keyspace - - test-answer - - deploy-pip-snapshot - - test-deployment - + pip install -r test/deployment/requirements.txt + cd test/deployment/ && python -m unittest test release: filter: owner: graknlabs branch: master - validation: - validate-dependencies: - machine: graknlabs-ubuntu-20.04 - script: bazel test //:release-validate-deps --test_output=streamed + # TODO: add it back once we're able to depend on @graknlabs_protocol as bazel rather than artifact dependency + # validation: + # validate-dependencies: + # machine: graknlabs-ubuntu-20.04 + # script: bazel test //:release-validate-deps --test_output=streamed deployment: deploy-github: machine: graknlabs-ubuntu-20.04 @@ -122,7 +149,7 @@ release: bazel run @graknlabs_dependencies//tool/release:create-notes -- client-python $(cat VERSION) ./RELEASE_TEMPLATE.md bazel clean --expunge export DEPLOY_GITHUB_TOKEN=$REPO_GITHUB_TOKEN - bazel run --define version=$(cat VERSION) //:deploy-github -- $CIRCLE_SHA1 + bazel run --define version=$(cat VERSION) //:deploy-github -- $GRABL_COMMIT deploy-pip-release: machine: graknlabs-ubuntu-20.04 script: | diff --git a/BUILD b/BUILD index 5657fc16..3d734b50 100644 --- a/BUILD +++ b/BUILD @@ -1,23 +1,24 @@ # -# Copyright (C) 2020 Grakn Labs +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at # -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. +# http://www.apache.org/licenses/LICENSE-2.0 # -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . +# 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. # exports_files(["requirements.txt", "deployment.bzl", "RELEASE_TEMPLATE.md"]) -load("@rules_python//python:defs.bzl", "py_library", "py_test") load("@graknlabs_client_python_pip//:requirements.bzl", graknlabs_client_python_requirement = "requirement") @@ -27,6 +28,7 @@ load("@graknlabs_bazel_distribution//github:rules.bzl", "deploy_github") load("@graknlabs_bazel_distribution//artifact:rules.bzl", "artifact_extractor") load("@graknlabs_dependencies//tool/release:rules.bzl", "release_validate_deps") +load("@graknlabs_dependencies//tool/checkstyle:rules.bzl", "checkstyle_test") load("@graknlabs_dependencies//distribution:deployment.bzl", "deployment") load(":deployment.bzl", github_deployment = "deployment") @@ -36,7 +38,7 @@ py_library( name = "client_python", srcs = glob(["grakn/**/*.py"]), deps = [ - "@graknlabs_protocol//grpc/python:protocol", + graknlabs_client_python_requirement("graknprotocol"), graknlabs_client_python_requirement("protobuf"), graknlabs_client_python_requirement("grpcio"), graknlabs_client_python_requirement("six"), @@ -44,6 +46,17 @@ py_library( visibility =["//visibility:public"] ) +checkstyle_test( + name = "checkstyle", + include = glob([ + "*", + ".grabl/automation.yml", + "grakn/**/*", + ]), + license_type = "apache", + size = "small", +) + assemble_pip( name = "assemble-pip", target = ":client_python", @@ -66,7 +79,12 @@ assemble_pip( author = "Grakn Labs", author_email = "community@grakn.ai", license = "Apache-2.0", - install_requires=['grpcio==1.24.1,<2', 'protobuf==3.6.1', 'six>=1.11.0'], + install_requires=[ + 'graknprotocol==0.0.0-6bf8c601ecd57a2869cde56c17eec8784a9a2804', + 'grpcio==1.33.2', + 'protobuf==3.6.1', + 'six>=1.11.0', + ], keywords = ["grakn", "database", "graph", "knowledgebase", "knowledge-engineering"], description = "Grakn Client for Python", long_description_file = "//:README.md", @@ -90,86 +108,20 @@ deploy_github( repository = github_deployment["github.repository"], ) -py_test( - name = "test_concept", - srcs = [ - "tests/integration/base.py", - "tests/integration/test_concept.py" - ], - deps = [ - ":client_python", - ], - data = ["@graknlabs_grakn_core_artifact//file"], - args = ["$(location @graknlabs_grakn_core_artifact//file)"], - python_version = "PY3" -) - -py_test( - name = "test_grakn", - srcs = [ - "tests/integration/base.py", - "tests/integration/test_grakn.py" - ], - deps = [ - ":client_python", - ], - data = ["@graknlabs_grakn_core_artifact//file"], - args = ["$(location @graknlabs_grakn_core_artifact//file)"], - python_version = "PY3" -) - -py_test( - name = "test_keyspace", - srcs = [ - "tests/integration/base.py", - "tests/integration/test_keyspace.py" - ], - deps = [ - ":client_python", - ], - data = ["@graknlabs_grakn_core_artifact//file"], - args = ["$(location @graknlabs_grakn_core_artifact//file)"], - python_version = "PY3" -) - -py_test( - name = "test_answer", - srcs = [ - "tests/integration/base.py", - "tests/integration/test_answer.py" - ], - deps = [ - ":client_python", - ], - size = "large", - data = ["@graknlabs_grakn_core_artifact//file"], - args = ["$(location @graknlabs_grakn_core_artifact//file)"], - python_version = "PY3" -) - -test_suite( - name = "test_integration", - tests = [ - ":test_concept", - ":test_grakn", - ":test_keyspace", - ":test_answer", - ] -) - artifact_extractor( name = "grakn-extractor", - artifact = "@graknlabs_grakn_core_artifact//file", + artifact = "@graknlabs_grakn_core_artifact_linux//file", ) -release_validate_deps( - name = "release-validate-deps", - refs = "@graknlabs_client_python_workspace_refs//:refs.json", - tagged_deps = [ - "@graknlabs_protocol", - ], - tags = ["manual"] # in order for bazel test //... to not fail -) +# TODO: add it back once we're able to depend on @graknlabs_protocol as bazel rather than artifact dependency +#release_validate_deps( +# name = "release-validate-deps", +# refs = "@graknlabs_client_python_workspace_refs//:refs.json", +# tagged_deps = [ +# "@graknlabs_protocol", +# ], +# tags = ["manual"] # in order for bazel test //... to not fail +#) # CI targets that are not declared in any BUILD file, but are called externally filegroup( diff --git a/WORKSPACE b/WORKSPACE index 49058f6a..32bf782e 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -1,18 +1,20 @@ # -# Copyright (C) 2020 Grakn Labs +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at # -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. +# http://www.apache.org/licenses/LICENSE-2.0 # -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . +# 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. # workspace(name = "graknlabs_client_python") @@ -59,6 +61,10 @@ graknlabs_dependencies_ci_pip() load("@graknlabs_dependencies_ci_pip//:requirements.bzl", graknlabs_dependencies_pip_install = "pip_install") graknlabs_dependencies_pip_install() +# Load //tool/checkstyle +load("@graknlabs_dependencies//tool/checkstyle:deps.bzl", checkstyle_deps = "deps") +checkstyle_deps() + ###################################### # Load @graknlabs_bazel_distribution # ###################################### @@ -82,19 +88,16 @@ graknlabs_bazel_distribution_pip_install() load("@graknlabs_bazel_distribution//github:deps.bzl", github_deps = "deps") github_deps() -############################ -# Load @graknlabs_protocol # -############################ - -load("//dependencies/graknlabs:repositories.bzl", "graknlabs_protocol") -graknlabs_protocol() +################################ +# Load @graknlabs dependencies # +################################ -####################################### -# Load @graknlabs_grakn_core_artifact # -####################################### +load("//dependencies/graknlabs:repositories.bzl", "graknlabs_common") +graknlabs_common() -load("//dependencies/graknlabs:artifacts.bzl", "graknlabs_grakn_core_artifact") -graknlabs_grakn_core_artifact() +# Load artifacts +load("//dependencies/graknlabs:artifacts.bzl", "graknlabs_grakn_core_artifacts") +graknlabs_grakn_core_artifacts() ################################# # Load @graknlabs_client_python # diff --git a/dependencies/graknlabs/BUILD b/dependencies/graknlabs/BUILD index fb0409e3..4162fab4 100644 --- a/dependencies/graknlabs/BUILD +++ b/dependencies/graknlabs/BUILD @@ -1,16 +1,26 @@ # -# Copyright (C) 2020 Grakn Labs +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at # -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. +# http://www.apache.org/licenses/LICENSE-2.0 # -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . +# 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. # + +load("@graknlabs_dependencies//tool/checkstyle:rules.bzl", "checkstyle_test") + +checkstyle_test( + name = "checkstyle", + include = glob(["*"]), + license_type = "apache", +) diff --git a/dependencies/graknlabs/artifacts.bzl b/dependencies/graknlabs/artifacts.bzl index 0ab4be1c..6d3f1e37 100644 --- a/dependencies/graknlabs/artifacts.bzl +++ b/dependencies/graknlabs/artifacts.bzl @@ -1,30 +1,31 @@ # -# Copyright (C) 2020 Grakn Labs +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at # -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. +# http://www.apache.org/licenses/LICENSE-2.0 # -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . +# 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. # -load("@graknlabs_bazel_distribution//artifact:rules.bzl", "artifact_file") +load("@graknlabs_dependencies//distribution/artifact:rules.bzl", "native_artifact_files") load("@graknlabs_dependencies//distribution:deployment.bzl", "deployment") -def graknlabs_grakn_core_artifact(): - artifact_file( +def graknlabs_grakn_core_artifacts(): + native_artifact_files( name = "graknlabs_grakn_core_artifact", group_name = "graknlabs_grakn_core", - artifact_name = "grakn-core-all-linux-{version}.tar.gz", - commit_source = deployment["artifact.snapshot"], + artifact_name = "grakn-core-server-{platform}-{version}.tar.gz", tag_source = deployment["artifact.release"], - # TODO - client-python is broken with 1.8.1, as current deps (eg. protocol) are preparing for Grakn 2.0 - tag = "1.8.1", + commit_source = deployment["artifact.snapshot"], + commit = "8e80542cd6afe3318859320565e8afe119e7ae11", ) diff --git a/dependencies/graknlabs/repositories.bzl b/dependencies/graknlabs/repositories.bzl index 3457ecd0..156543bb 100644 --- a/dependencies/graknlabs/repositories.bzl +++ b/dependencies/graknlabs/repositories.bzl @@ -1,18 +1,20 @@ # -# Copyright (C) 2020 Grakn Labs +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at # -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. +# http://www.apache.org/licenses/LICENSE-2.0 # -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . +# 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. # load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository") @@ -21,12 +23,12 @@ def graknlabs_dependencies(): git_repository( name = "graknlabs_dependencies", remote = "https://github.com/graknlabs/dependencies", - commit = "1c86421327bec68a83c3f88d728add07010f797a", # sync-marker: do not remove this comment, this is used for sync-dependencies by @graknlabs_dependencies + commit = "ed2c074bea897dada26e4f112c1d08f739e90012", # sync-marker: do not remove this comment, this is used for sync-dependencies by @graknlabs_dependencies ) -def graknlabs_protocol(): +def graknlabs_common(): git_repository( - name = "graknlabs_protocol", - remote = "https://github.com/graknlabs/protocol", - tag = "1.0.7", # sync-marker: do not remove this comment, this is used for sync-dependencies by @graknlabs_protocol + name = "graknlabs_common", + remote = "https://github.com/graknlabs/common", + commit = "cfd261fd5412a0b45cb5494555e4491dd3ce5f64" # sync-marker: do not remove this comment, this is used for sync-dependencies by @graknlabs_common ) diff --git a/deployment.bzl b/deployment.bzl index c41b5eb4..098c1de2 100644 --- a/deployment.bzl +++ b/deployment.bzl @@ -1,18 +1,20 @@ # -# Copyright (C) 2020 Grakn Labs +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at # -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. +# http://www.apache.org/licenses/LICENSE-2.0 # -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . +# 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. # deployment = { diff --git a/grakn/client.py b/grakn/client.py index 6ca2642d..479b0bf1 100644 --- a/grakn/client.py +++ b/grakn/client.py @@ -18,33 +18,31 @@ # import grpc +import sched +import time -from grakn.service.Session.util.enums import ValueType # user-facing ValueType enum +from grakn.options import GraknOptions +from grakn.rpc.database_manager import DatabaseManager as _DatabaseManager +from grakn.rpc.session import Session as _Session, SessionType -from grakn.service.Session.util.RequestBuilder import RequestBuilder, QueryOptions -from grakn.service.Session.util.enums import TxType as _TxType -from grakn.service.Keyspace.KeyspaceService import KeyspaceService -from grakn.service.Session.TransactionService import TransactionService -from grakn_protocol.session.Session_pb2_grpc import SessionServiceStub -from grakn.exception.GraknError import GraknError +# Repackaging these enums allows users to import everything they (most likely) need from "grakn.client" +from grakn.rpc.transaction import TransactionType # noqa # pylint: disable=unused-import +from grakn.concept.type.attribute_type import ValueType # noqa # pylint: disable=unused-import class GraknClient(object): - """ A client/representation of a Grakn instance""" + DEFAULT_URI = "localhost:1729" - def __init__(self, uri, credentials=None): - self.uri = uri - self.credentials = credentials - self._channel = grpc.insecure_channel(uri) - self._keyspace_service = KeyspaceService(self.uri, self._channel, credentials) + def __init__(self, address=DEFAULT_URI): + self._channel = grpc.insecure_channel(address) + self._databases = _DatabaseManager(self._channel) + self._scheduler = sched.scheduler(time.time, time.sleep) - def session(self, keyspace): - """ Open a session for a specific keyspace. Can be used as `with Grakn('localhost:48555').session(keyspace='test') as session: ... ` or as normal assignment""" - return Session(self.uri, keyspace, self._channel, self.credentials) - session.__annotations__ = {'keyspace': str} + def session(self, database: str, session_type: SessionType, options=GraknOptions()): + return _Session(self, database, session_type, options) - def keyspaces(self): - return self._keyspace_service + def databases(self): + return self._databases def close(self): self._channel.close() @@ -52,165 +50,9 @@ def close(self): def __enter__(self): return self - def __exit__(self, type, value, tb): + def __exit__(self, exc_type, exc_val, exc_tb): self.close() - if tb is None: - # No exception + if exc_tb is None: pass else: - #print("Closing Client due to exception: {0} \n traceback: \n {1}".format(type, tb)) return False - - -class Session(object): - """ A session for a Grakn instance and a specific keyspace """ - - def __init__(self, uri, keyspace, channel, credentials): - - if not isinstance(uri, str): - raise TypeError('expected string for uri') - - if not isinstance(keyspace, str): - raise TypeError('expected string for keyspace') - - self.keyspace = keyspace - self.uri = uri - self.credentials = credentials - - self._stub = SessionServiceStub(channel) - self._closed = False - - try: - open_session_response = self._stub.open(RequestBuilder.open_session(keyspace, self.credentials)) - self.session_id = open_session_response.sessionId - except Exception as e: - raise GraknError('Could not obtain sessionId for keyspace "{0}", stems from: {1}'.format(keyspace, e)) - - __init__.__annotations__ = {'uri': str, 'keyspace': str} - - def transaction(self): - """ Build a read or write transaction to Grakn on this keyspace (ie. session.transaction().read() or .write()) """ - if self._closed: - raise GraknError("Session is closed") - - # create a transaction service which hides GRPC usage - return TransactionBuilder(self.session_id, self._stub.transaction) - - def close(self): - """ Close this keyspace session """ - close_session_req = RequestBuilder.close_session(self.session_id) - self._stub.close(close_session_req) - self._closed = True - - def __enter__(self): - return self - - def __exit__(self, type, value, tb): - self.close() - if tb is None: - # No exception - pass - else: - #print("Closing Session due to exception: {0} \n traceback: \n {1}".format(type, tb)) - return False - - -class TransactionBuilder(object): - def __init__(self, session_id, transaction_rpc_constructor): - self._session_id = session_id - self._transaction_rpc_constructor = transaction_rpc_constructor - - def read(self): - transaction_service = TransactionService(self._session_id, _TxType.READ, self._transaction_rpc_constructor) - return Transaction(transaction_service) - - def write(self): - transaction_service = TransactionService(self._session_id, _TxType.WRITE, self._transaction_rpc_constructor) - return Transaction(transaction_service) - - -class Transaction(object): - """ Presents the Grakn interface to the user, actual work with GRPC happens in TransactionService """ - - Options = QueryOptions - - def __init__(self, transaction_service): - self._tx_service = transaction_service - __init__.__annotations__ = {'transaction_service': TransactionService} - - def __enter__(self): - return self - - def __exit__(self, type, value, tb): - self.close() - if tb is None: - # No exception - pass - else: - #print("Closing Transaction due to exception: {0} \n traceback: \n {1}".format(type, tb)) - return False - - def query(self, query, infer=Options.SERVER_DEFAULT, explain=Options.SERVER_DEFAULT, batch_size=Options.SERVER_DEFAULT): - """ Execute a Graql query with query options""" - return self._tx_service.query(query, infer, explain, batch_size) - query.__annotations__ = {'query': str} - - def commit(self): - """ Commit and close this transaction, persisting changes to Grakn """ - self._tx_service.commit() - self.close() - - def close(self): - """ Close this transaction without committing """ - self._tx_service.close() # close the service - - def is_open(self): - """ Check if this transaction is open""" - return not self._tx_service.is_closed() - - def get_concept(self, concept_id): - """ Retrieve a concept by Concept ID (string) """ - return self._tx_service.get_concept(concept_id) - get_concept.__annotations__ = {'concept_id': str} - - def get_schema_concept(self, label): - """ Retrieve a schema concept by its label (eg. those defined using `define` or tx.put...() """ - return self._tx_service.get_schema_concept(label) - get_schema_concept.__annotations__ = {'label': str} - - def get_attributes_by_value(self, attribute_value, value_type): - """ Retrieve atttributes with a specific value and value type - - :param any attribute_value: the value to match - :param grakn.ValueType value_type: The value type of the value in Grakn, as given by the grakn.ValueType enum - """ - return self._tx_service.get_attributes_by_value(attribute_value, value_type) - - def put_entity_type(self, label): - """ Define a new entity type with the given label """ - return self._tx_service.put_entity_type(label) - put_entity_type.__annotations__ = {'label': str} - - def put_relation_type(self, label): - """ Define a new relation type with the given label """ - return self._tx_service.put_relation_type(label) - put_relation_type.__annotations__ = {'label': str} - - def put_attribute_type(self, label, value_type): - """ Define a new attribute type with the given label and value type - - :param str label: the label of the attribute type - :param grakn.ValueType value_type: the data type of the value to be stored, as given by the grakn.ValueType enum - """ - return self._tx_service.put_attribute_type(label, value_type) - put_attribute_type.__annotations__ = {'label': str} - - def put_role(self, label): - """ Define a role with the given label """ - return self._tx_service.put_role(label) - put_role.__annotations__ = {'label': str} - - def put_rule(self, label, when, then): - """ Define a new rule with the given label, when and then clauses """ - return self._tx_service.put_rule(label, when, then) - put_rule.__annotations__ = {'label': str, 'when': str, 'then': str} diff --git a/grakn/exception/GraknError.py b/grakn/common/exception.py similarity index 94% rename from grakn/exception/GraknError.py rename to grakn/common/exception.py index 022801e0..c075317e 100644 --- a/grakn/exception/GraknError.py +++ b/grakn/common/exception.py @@ -6,9 +6,9 @@ # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY @@ -17,5 +17,5 @@ # under the License. # -class GraknError(Exception): +class GraknClientException(Exception): pass diff --git a/grakn/concept/answer/answer.py b/grakn/concept/answer/answer.py new file mode 100644 index 00000000..e19b2b0c --- /dev/null +++ b/grakn/concept/answer/answer.py @@ -0,0 +1,35 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +import graknprotocol.protobuf.answer_pb2 as answer_proto + +from grakn.common.exception import GraknClientException +from grakn.concept.answer import concept_map + + +class Answer(object): + + _CONCEPT_MAP = "concept_map" + + +def _of(proto_answer: answer_proto.Answer): + answer_case = proto_answer.WhichOneof("answer") + if answer_case == Answer._CONCEPT_MAP: + return concept_map._of(proto_answer.concept_map) + raise GraknClientException("The answer type " + answer_case + " was not recognised.") diff --git a/grakn/concept/answer/concept_map.py b/grakn/concept/answer/concept_map.py new file mode 100644 index 00000000..a8f66e1e --- /dev/null +++ b/grakn/concept/answer/concept_map.py @@ -0,0 +1,77 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +from typing import Mapping + +import graknprotocol.protobuf.answer_pb2 as answer_proto + +from grakn.common.exception import GraknClientException +from grakn.concept.proto import concept_proto_reader +from grakn.concept.answer.answer import Answer +from grakn.concept.concept import Concept + + +class ConceptMap(Answer): + + _THING = "thing" + + def __init__(self, mapping: Mapping[str, Concept], query_pattern: str): + self._map = mapping + self._query_pattern = query_pattern + + def query_pattern(self): + return self._query_pattern + + def map(self): + return self._map + + def concepts(self): + return self._map.values() + + def get(self, variable: str): + concept = self._map[variable] + if not concept: + raise GraknClientException("The variable " + variable + " does not exist.") + return concept + + def __str__(self): + return "".join(map(lambda var: "[" + var + "/" + str(self._map[var]) + "]", sorted(self._map.keys()))) + + def __eq__(self, other): + if other is self: + return True + if not other or type(other) != type(self): + return False + return other._map == self._map + + def __hash__(self): + return hash(self._map) + + +def _of(concept_map_proto: answer_proto.ConceptMap): + variable_map = {} + for res_var in concept_map_proto.map: + res_concept = concept_map_proto.map[res_var] + if res_concept.HasField(ConceptMap._THING): + concept = concept_proto_reader.thing(res_concept.thing) + else: + concept = concept_proto_reader.type_(res_concept.type) + variable_map[res_var] = concept + query_pattern = None if concept_map_proto.pattern == "" else concept_map_proto.pattern + return ConceptMap(variable_map, query_pattern) diff --git a/grakn/concept/concept.py b/grakn/concept/concept.py new file mode 100644 index 00000000..47436652 --- /dev/null +++ b/grakn/concept/concept.py @@ -0,0 +1,48 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + + +class Concept(object): + + def is_type(self): + return False + + def is_thing(self): + return False + + def is_remote(self): + return False + + +class RemoteConcept(object): + + def is_remote(self): + return True + + def delete(self): + pass + + def is_deleted(self): + return False + + def is_type(self): + return False + + def is_thing(self): + return False diff --git a/grakn/concept/concept_manager.py b/grakn/concept/concept_manager.py new file mode 100644 index 00000000..36bdc60b --- /dev/null +++ b/grakn/concept/concept_manager.py @@ -0,0 +1,103 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +import graknprotocol.protobuf.concept_pb2 as concept_proto +import graknprotocol.protobuf.transaction_pb2 as transaction_proto + +from grakn.concept.proto import concept_proto_reader, concept_proto_builder +from grakn.concept.type.entity_type import EntityType +from grakn.concept.type.relation_type import RelationType + + +class ConceptManager(object): + + def __init__(self, transaction): + self._transaction = transaction + + def get_root_thing_type(self): + return self.get_type("thing") + + def get_root_entity_type(self): + return self.get_entity_type("entity") + + def get_root_relation_type(self): + return self.get_relation_type("relation") + + def get_root_attribute_type(self): + return self.get_attribute_type("attribute") + + def put_entity_type(self, label: str): + req = concept_proto.ConceptManager.Req() + put_entity_type_req = concept_proto.ConceptManager.PutEntityType.Req() + put_entity_type_req.label = label + req.put_entity_type_req.CopyFrom(put_entity_type_req) + res = self._execute(req) + return EntityType._of(res.put_entity_type_res.entity_type) + + def get_entity_type(self, label: str): + _type = self.get_type(label) + return _type if _type.is_entity_type() else None + + def put_relation_type(self, label: str): + req = concept_proto.ConceptManager.Req() + put_relation_type_req = concept_proto.ConceptManager.PutRelationType.Req() + put_relation_type_req.label = label + req.put_relation_type_req.CopyFrom(put_relation_type_req) + res = self._execute(req) + return RelationType._of(res.put_relation_type_res.relation_type) + + def get_relation_type(self, label: str): + _type = self.get_type(label) + return _type if _type.is_relation_type() else None + + def put_attribute_type(self, label: str, value_type): + req = concept_proto.ConceptManager.Req() + put_attribute_type_req = concept_proto.ConceptManager.PutAttributeType.Req() + put_attribute_type_req.label = label + put_attribute_type_req.value_type = concept_proto_builder.value_type(value_type) + req.put_attribute_type_req.CopyFrom(put_attribute_type_req) + res = self._execute(req) + return concept_proto_reader.attribute_type(res.put_attribute_type_res.attribute_type) + + def get_attribute_type(self, label: str): + _type = self.get_type(label) + return _type if _type.is_attribute_type() else None + + def get_thing(self, iid: str): + req = concept_proto.ConceptManager.Req() + get_thing_req = concept_proto.ConceptManager.GetThing.Req() + get_thing_req.iid = concept_proto_builder.iid(iid) + req.get_thing_req.CopyFrom(get_thing_req) + + response = self._execute(req) + return concept_proto_reader.thing(response.get_thing_res.thing) if response.get_thing_res.WhichOneof("res") == "thing" else None + + def get_type(self, label: str): + req = concept_proto.ConceptManager.Req() + get_type_req = concept_proto.ConceptManager.GetType.Req() + get_type_req.label = label + req.get_type_req.CopyFrom(get_type_req) + + response = self._execute(req) + return concept_proto_reader.type_(response.get_type_res.type) if response.get_type_res.WhichOneof("res") == "type" else None + + def _execute(self, request: concept_proto.ConceptManager.Req): + req = transaction_proto.Transaction.Req() + req.concept_manager_req.CopyFrom(request) + return self._transaction._execute(req).concept_manager_res diff --git a/grakn/concept/proto/concept_proto_builder.py b/grakn/concept/proto/concept_proto_builder.py new file mode 100644 index 00000000..ba4c473a --- /dev/null +++ b/grakn/concept/proto/concept_proto_builder.py @@ -0,0 +1,125 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +from datetime import datetime +from typing import List + +import graknprotocol.protobuf.concept_pb2 as concept_proto + +from grakn.common.exception import GraknClientException +from grakn.concept.type.value_type import ValueType + + +def thing(thing_): + proto_thing = concept_proto.Thing() + proto_thing.iid = bytes.fromhex(thing_.get_iid()) + proto_thing.encoding = thing_encoding(thing_) + return proto_thing + + +def type_(_type): + proto_type = concept_proto.Type() + proto_type.label = _type.get_label() + proto_type.encoding = type_encoding(_type) + + if _type.is_role_type(): + proto_type.scope = _type.get_scope() + + return proto_type + + +def types(types_: List): + return map(lambda _type: type_(_type), types_) + + +def boolean_attribute_value(value: bool): + value_proto = concept_proto.Attribute.Value() + value_proto.boolean = value + return value_proto + + +def long_attribute_value(value: int): + value_proto = concept_proto.Attribute.Value() + value_proto.long = value + return value_proto + + +def double_attribute_value(value: float): + value_proto = concept_proto.Attribute.Value() + value_proto.double = value + return value_proto + + +def string_attribute_value(value: str): + value_proto = concept_proto.Attribute.Value() + value_proto.string = value + return value_proto + + +def datetime_attribute_value(value: datetime): + value_proto = concept_proto.Attribute.Value() + value_proto.date_time = int((value - datetime(1970, 1, 1)).total_seconds() * 1000) + return value_proto + + +def value_type(value_type_: ValueType): + if value_type_ == ValueType.BOOLEAN: + return concept_proto.AttributeType.ValueType.Value("BOOLEAN") + elif value_type_ == ValueType.LONG: + return concept_proto.AttributeType.ValueType.Value("LONG") + elif value_type_ == ValueType.DOUBLE: + return concept_proto.AttributeType.ValueType.Value("DOUBLE") + elif value_type_ == ValueType.STRING: + return concept_proto.AttributeType.ValueType.Value("STRING") + elif value_type_ == ValueType.DATETIME: + return concept_proto.AttributeType.ValueType.Value("DATETIME") + elif value_type_ == ValueType.OBJECT: + return concept_proto.AttributeType.ValueType.Value("OBJECT") + else: + raise GraknClientException("Unrecognised value type: " + str(value_type_)) + + +def iid(iid_: str): + return bytes.fromhex(iid_) + + +def thing_encoding(thing_): + if thing_.is_entity(): + return concept_proto.Thing.Encoding.Value("ENTITY") + elif thing_.is_relation(): + return concept_proto.Thing.Encoding.Value("RELATION") + elif thing_.is_attribute(): + return concept_proto.Thing.Encoding.Value("ATTRIBUTE") + else: + raise GraknClientException("Unrecognised thing encoding: " + str(thing_)) + + +def type_encoding(_type): + if _type.is_entity_type(): + return concept_proto.Type.Encoding.Value("ENTITY_TYPE") + elif _type.is_relation_type(): + return concept_proto.Type.Encoding.Value("RELATION_TYPE") + elif _type.is_attribute_type(): + return concept_proto.Type.Encoding.Value("ATTRIBUTE_TYPE") + elif _type.is_role_type(): + return concept_proto.Type.Encoding.Value("ROLE_TYPE") + elif _type.is_thing_type(): + return concept_proto.Type.Encoding.Value("THING_TYPE") + else: + raise GraknClientException("Unrecognised type encoding: " + str(_type)) diff --git a/grakn/concept/proto/concept_proto_reader.py b/grakn/concept/proto/concept_proto_reader.py new file mode 100644 index 00000000..3fc2b27d --- /dev/null +++ b/grakn/concept/proto/concept_proto_reader.py @@ -0,0 +1,95 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +import graknprotocol.protobuf.concept_pb2 as concept_proto + +from grakn.common.exception import GraknClientException +from grakn.concept.thing.attribute import BooleanAttribute, LongAttribute, DoubleAttribute, StringAttribute, \ + DateTimeAttribute +from grakn.concept.thing.entity import Entity +from grakn.concept.thing.relation import Relation +from grakn.concept.type.attribute_type import BooleanAttributeType, LongAttributeType, DoubleAttributeType, \ + StringAttributeType, DateTimeAttributeType, AttributeType +from grakn.concept.type.entity_type import EntityType +from grakn.concept.type.relation_type import RelationType +from grakn.concept.type.role_type import RoleType +from grakn.concept.type.thing_type import ThingType + + +def thing(thing_proto: concept_proto.Thing): + if thing_proto.encoding == concept_proto.Thing.Encoding.Value("ENTITY"): + return Entity._of(thing_proto) + elif thing_proto.encoding == concept_proto.Thing.Encoding.Value("RELATION"): + return Relation._of(thing_proto) + elif thing_proto.encoding == concept_proto.Thing.Encoding.Value("ATTRIBUTE"): + return attribute(thing_proto) + else: + raise GraknClientException("The encoding " + thing_proto.encoding + " was not recognised.") + + +def attribute(thing_proto: concept_proto.Thing): + if thing_proto.value_type == concept_proto.AttributeType.ValueType.Value("BOOLEAN"): + return BooleanAttribute._of(thing_proto) + elif thing_proto.value_type == concept_proto.AttributeType.ValueType.Value("LONG"): + return LongAttribute._of(thing_proto) + elif thing_proto.value_type == concept_proto.AttributeType.ValueType.Value("DOUBLE"): + return DoubleAttribute._of(thing_proto) + elif thing_proto.value_type == concept_proto.AttributeType.ValueType.Value("STRING"): + return StringAttribute._of(thing_proto) + elif thing_proto.value_type == concept_proto.AttributeType.ValueType.Value("DATETIME"): + return DateTimeAttribute._of(thing_proto) + else: + raise GraknClientException("The value type " + str(thing_proto.value_type) + " was not recognised.") + + +def type_(type_proto: concept_proto.Type): + if type_proto.encoding == concept_proto.Type.Encoding.Value("ROLE_TYPE"): + return RoleType._of(type_proto) + else: + return thing_type(type_proto) + + +def thing_type(type_proto: concept_proto.Type): + if type_proto.encoding == concept_proto.Type.Encoding.Value("ENTITY_TYPE"): + return EntityType._of(type_proto) + elif type_proto.encoding == concept_proto.Type.Encoding.Value("RELATION_TYPE"): + return RelationType._of(type_proto) + elif type_proto.encoding == concept_proto.Type.Encoding.Value("ATTRIBUTE_TYPE"): + return attribute_type(type_proto) + elif type_proto.encoding == concept_proto.Type.Encoding.Value("THING_TYPE"): + return ThingType(type_proto.label, type_proto.root) + else: + raise GraknClientException("The encoding " + str(type_proto.encoding) + " was not recognised.") + + +def attribute_type(type_proto: concept_proto.Type): + if type_proto.value_type == concept_proto.AttributeType.ValueType.Value("BOOLEAN"): + return BooleanAttributeType._of(type_proto) + elif type_proto.value_type == concept_proto.AttributeType.ValueType.Value("LONG"): + return LongAttributeType._of(type_proto) + elif type_proto.value_type == concept_proto.AttributeType.ValueType.Value("DOUBLE"): + return DoubleAttributeType._of(type_proto) + elif type_proto.value_type == concept_proto.AttributeType.ValueType.Value("STRING"): + return StringAttributeType._of(type_proto) + elif type_proto.value_type == concept_proto.AttributeType.ValueType.Value("DATETIME"): + return DateTimeAttributeType._of(type_proto) + elif type_proto.value_type == concept_proto.AttributeType.ValueType.Value("OBJECT"): + return AttributeType(type_proto.label, type_proto.root) + else: + raise GraknClientException("The value type " + str(type_proto.value_type) + " was not recognised.") diff --git a/grakn/concept/thing/attribute.py b/grakn/concept/thing/attribute.py new file mode 100644 index 00000000..7e08ecc7 --- /dev/null +++ b/grakn/concept/thing/attribute.py @@ -0,0 +1,255 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +from datetime import datetime + +import graknprotocol.protobuf.concept_pb2 as concept_proto + +from grakn.concept.proto import concept_proto_builder +from grakn.concept.thing.thing import Thing, RemoteThing + + +class Attribute(Thing): + + def is_attribute(self): + return True + + def is_boolean(self): + return False + + def is_long(self): + return False + + def is_double(self): + return False + + def is_string(self): + return False + + def is_datetime(self): + return False + + +class RemoteAttribute(RemoteThing): + + def get_owners(self, owner_type=None): + method = concept_proto.Thing.Req() + get_owners_req = concept_proto.Attribute.GetOwners.Req() + if owner_type: + get_owners_req.thing_type = concept_proto_builder.type_(owner_type) + method.attribute_get_owners_req.CopyFrom(get_owners_req) + return self._thing_stream(method, lambda res: res.attribute_get_owners_res.things) + + def is_attribute(self): + return True + + def is_boolean(self): + return False + + def is_long(self): + return False + + def is_double(self): + return False + + def is_string(self): + return False + + def is_datetime(self): + return False + + +class BooleanAttribute(Attribute): + + def __init__(self, iid: str, value: bool): + super(BooleanAttribute, self).__init__(iid) + self._value = value + + @staticmethod + def _of(thing_proto: concept_proto.Thing): + return BooleanAttribute(thing_proto.iid.hex(), thing_proto.value.boolean) + + def get_value(self): + return self._value + + def is_boolean(self): + return True + + def as_remote(self, transaction): + return RemoteBooleanAttribute(transaction, self.get_iid(), self.get_value()) + + +class RemoteBooleanAttribute(RemoteAttribute): + + def __init__(self, transaction, iid: str, value: bool): + super(RemoteBooleanAttribute, self).__init__(transaction, iid) + self._value = value + + def get_value(self): + return self._value + + def is_boolean(self): + return True + + def as_remote(self, transaction): + return RemoteBooleanAttribute(transaction, self.get_iid(), self.get_value()) + + +class LongAttribute(Attribute): + + def __init__(self, iid: str, value: int): + super(LongAttribute, self).__init__(iid) + self._value = value + + @staticmethod + def _of(thing_proto: concept_proto.Thing): + return LongAttribute(thing_proto.iid.hex(), thing_proto.value.long) + + def get_value(self): + return self._value + + def is_long(self): + return True + + def as_remote(self, transaction): + return RemoteLongAttribute(transaction, self.get_iid(), self.get_value()) + + +class RemoteLongAttribute(RemoteAttribute): + + def __init__(self, transaction, iid: str, value: int): + super(RemoteLongAttribute, self).__init__(transaction, iid) + self._value = value + + def get_value(self): + return self._value + + def is_long(self): + return True + + def as_remote(self, transaction): + return RemoteLongAttribute(transaction, self.get_iid(), self.get_value()) + + +class DoubleAttribute(Attribute): + + def __init__(self, iid: str, value: float): + super(DoubleAttribute, self).__init__(iid) + self._value = value + + @staticmethod + def _of(thing_proto: concept_proto.Thing): + return DoubleAttribute(thing_proto.iid.hex(), thing_proto.value.double) + + def get_value(self): + return self._value + + def is_double(self): + return True + + def as_remote(self, transaction): + return RemoteDoubleAttribute(transaction, self.get_iid(), self.get_value()) + + +class RemoteDoubleAttribute(RemoteAttribute): + + def __init__(self, transaction, iid: str, value: float): + super(RemoteDoubleAttribute, self).__init__(transaction, iid) + self._value = value + + def get_value(self): + return self._value + + def is_double(self): + return True + + def as_remote(self, transaction): + return RemoteDoubleAttribute(transaction, self.get_iid(), self.get_value()) + + +class StringAttribute(Attribute): + + def __init__(self, iid: str, value: str): + super(StringAttribute, self).__init__(iid) + self._value = value + + @staticmethod + def _of(thing_proto: concept_proto.Thing): + return StringAttribute(thing_proto.iid.hex(), thing_proto.value.string) + + def get_value(self): + return self._value + + def is_string(self): + return True + + def as_remote(self, transaction): + return RemoteStringAttribute(transaction, self.get_iid(), self.get_value()) + + +class RemoteStringAttribute(RemoteAttribute): + + def __init__(self, transaction, iid: str, value: str): + super(RemoteStringAttribute, self).__init__(transaction, iid) + self._value = value + + def get_value(self): + return self._value + + def is_string(self): + return True + + def as_remote(self, transaction): + return RemoteStringAttribute(transaction, self.get_iid(), self.get_value()) + + +class DateTimeAttribute(Attribute): + + def __init__(self, iid: str, value: datetime): + super(DateTimeAttribute, self).__init__(iid) + self._value = value + + @staticmethod + def _of(thing_proto: concept_proto.Thing): + return DateTimeAttribute(thing_proto.iid.hex(), datetime.fromtimestamp(float(thing_proto.value.date_time) / 1000.0)) + + def get_value(self): + return self._value + + def is_datetime(self): + return True + + def as_remote(self, transaction): + return RemoteDateTimeAttribute(transaction, self.get_iid(), self.get_value()) + + +class RemoteDateTimeAttribute(RemoteAttribute): + + def __init__(self, transaction, iid: str, value: datetime): + super(RemoteDateTimeAttribute, self).__init__(transaction, iid) + self._value = value + + def get_value(self): + return self._value + + def is_datetime(self): + return True + + def as_remote(self, transaction): + return RemoteDateTimeAttribute(transaction, self.get_iid(), self.get_value()) diff --git a/grakn/concept/thing/entity.py b/grakn/concept/thing/entity.py new file mode 100644 index 00000000..971ffb74 --- /dev/null +++ b/grakn/concept/thing/entity.py @@ -0,0 +1,44 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +import graknprotocol.protobuf.concept_pb2 as concept_proto + +from grakn.concept.thing.thing import Thing, RemoteThing + + +class Entity(Thing): + + @staticmethod + def _of(thing_proto: concept_proto.Thing): + return Entity(thing_proto.iid.hex()) + + def as_remote(self, transaction): + return RemoteEntity(transaction, self._iid) + + def is_entity(self): + return True + + +class RemoteEntity(RemoteThing): + + def as_remote(self, transaction): + return RemoteEntity(transaction, self._iid) + + def is_entity(self): + return True diff --git a/grakn/concept/thing/relation.py b/grakn/concept/thing/relation.py new file mode 100644 index 00000000..6a28b9ca --- /dev/null +++ b/grakn/concept/thing/relation.py @@ -0,0 +1,89 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +import graknprotocol.protobuf.concept_pb2 as concept_proto +import graknprotocol.protobuf.transaction_pb2 as transaction_proto + +from grakn.concept.proto import concept_proto_builder, concept_proto_reader +from grakn.concept.thing.thing import Thing, RemoteThing + + +class Relation(Thing): + + @staticmethod + def _of(thing_proto: concept_proto.Thing): + return Relation(thing_proto.iid.hex()) + + def as_remote(self, transaction): + return RemoteRelation(transaction, self.get_iid()) + + def is_relation(self): + return True + + +class RemoteRelation(RemoteThing): + + def as_remote(self, transaction): + return RemoteRelation(transaction, self.get_iid()) + + def get_players_by_role_type(self): + method = concept_proto.Thing.Req() + method.relation_get_players_by_role_type_req.CopyFrom(concept_proto.Relation.GetPlayersByRoleType.Req()) + method.iid = concept_proto_builder.iid(self.get_iid()) + + request = transaction_proto.Transaction.Req() + request.thing_req.CopyFrom(method) + stream = self._transaction._stream(request, lambda res: res.thing_res.relation_get_players_by_role_type_res.role_types_with_players) + + role_player_dict = {} + for role_player in stream: + role = concept_proto_reader.type_(role_player.role_type) + player = concept_proto_reader.thing(role_player.player) + if role not in role_player_dict: + role_player_dict[role] = [] + role_player_dict[role].append(player) + return role_player_dict + + def get_players(self, role_types=None): + if not role_types: + role_types = [] + method = concept_proto.Thing.Req() + get_players_req = concept_proto.Relation.GetPlayers.Req() + get_players_req.role_types.extend(concept_proto_builder.types(role_types)) + method.relation_get_players_req.CopyFrom(get_players_req) + return self._thing_stream(method, lambda res: res.relation_get_players_res.things) + + def add_player(self, role_type, player): + method = concept_proto.Thing.Req() + add_player_req = concept_proto.Relation.AddPlayer.Req() + add_player_req.role_type.CopyFrom(concept_proto_builder.type_(role_type)) + add_player_req.player.CopyFrom(concept_proto_builder.thing(player)) + method.relation_add_player_req.CopyFrom(add_player_req) + self._execute(method) + + def remove_player(self, role_type, player): + method = concept_proto.Thing.Req() + remove_player_req = concept_proto.Relation.RemovePlayer.Req() + remove_player_req.role_type.CopyFrom(concept_proto_builder.type_(role_type)) + remove_player_req.player.CopyFrom(concept_proto_builder.thing(player)) + method.relation_remove_player_req.CopyFrom(remove_player_req) + self._execute(method) + + def is_relation(self): + return True diff --git a/grakn/concept/thing/thing.py b/grakn/concept/thing/thing.py new file mode 100644 index 00000000..c5683631 --- /dev/null +++ b/grakn/concept/thing/thing.py @@ -0,0 +1,183 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +from typing import Callable, List + +import graknprotocol.protobuf.concept_pb2 as concept_proto +import graknprotocol.protobuf.transaction_pb2 as transaction_proto + +from grakn.common.exception import GraknClientException +from grakn.concept.proto import concept_proto_reader, concept_proto_builder +from grakn.concept.concept import Concept, RemoteConcept + + +class Thing(Concept): + + def __init__(self, iid: str): + if not iid: + raise GraknClientException("IID must be a non-empty string.") + self._iid = iid + self._hash = hash(iid) + + def get_iid(self): + return self._iid + + def is_thing(self): + return True + + def is_entity(self): + return False + + def is_attribute(self): + return False + + def is_relation(self): + return False + + def __str__(self): + return type(self).__name__ + "[iid:" + self.get_iid() + "]" + + def __eq__(self, other): + if other is self: + return True + if not other or type(self) != type(other): + return False + return self.get_iid() == other.get_iid() + + def __hash__(self): + return self._hash + + +class RemoteThing(RemoteConcept): + + def __init__(self, transaction, iid: str): + if not transaction: + raise GraknClientException("Transaction must be set.") + if not iid: + raise GraknClientException("IID must be set.") + self._transaction = transaction + self._iid = iid + self._hash = hash(iid) + + def get_iid(self): + return self._iid + + def get_type(self): + method = concept_proto.Thing.Req() + method.thing_get_type_req.CopyFrom(concept_proto.Thing.GetType.Req()) + return concept_proto_reader.type_(self._execute(method).thing_get_type_res.thing_type) + + def is_inferred(self): + req = concept_proto.Thing.Req() + req.thing_is_inferred_req.CopyFrom(concept_proto.Thing.IsInferred.Req()) + return self._execute(req).thing_is_inferred_res.inferred + + def get_has(self, attribute_type=None, attribute_types: List = None, only_key=False): + if [bool(attribute_type), bool(attribute_types), only_key].count(True) > 1: + raise GraknClientException("Only one filter can be applied at a time to get_has." + "The possible filters are: [attribute_type, attribute_types, only_key]") + if attribute_type: + attribute_types = [attribute_type] + method = concept_proto.Thing.Req() + get_has_req = concept_proto.Thing.GetHas.Req() + if only_key: + get_has_req.keys_only = only_key + elif attribute_types: + get_has_req.attribute_types.extend(concept_proto_builder.types(attribute_types)) + method.thing_get_has_req.CopyFrom(get_has_req) + return self._thing_stream(method, lambda res: res.thing_get_has_res.attributes) + + def get_plays(self): + req = concept_proto.Thing.Req() + req.thing_get_plays_req.CopyFrom(concept_proto.Thing.GetPlays.Req()) + return self._type_stream(req, lambda res: res.thing_get_plays_res.role_types) + + def get_relations(self, role_types=None): + if not role_types: + role_types = [] + method = concept_proto.Thing.Req() + get_relations_req = concept_proto.Thing.GetRelations.Req() + get_relations_req.role_types.extend(concept_proto_builder.types(role_types)) + method.thing_get_relations_req.CopyFrom(get_relations_req) + return self._thing_stream(method, lambda res: res.thing_get_relations_res.relations) + + def set_has(self, attribute): + method = concept_proto.Thing.Req() + set_has_req = concept_proto.Thing.SetHas.Req() + set_has_req.attribute.CopyFrom(concept_proto_builder.thing(attribute)) + method.thing_set_has_req.CopyFrom(set_has_req) + self._execute(method) + + def unset_has(self, attribute): + method = concept_proto.Thing.Req() + unset_has_req = concept_proto.Thing.UnsetHas.Req() + unset_has_req.attribute.CopyFrom(concept_proto_builder.thing(attribute)) + method.thing_unset_has_req.CopyFrom(unset_has_req) + self._execute(method) + + def delete(self): + method = concept_proto.Thing.Req() + method.thing_delete_req.CopyFrom(method.concept_proto.Thing.Delete.Req()) + self._execute(method) + + def is_deleted(self): + return not self._transaction.concepts().get_thing(self.get_iid()) + + def is_thing(self): + return True + + def is_entity(self): + return False + + def is_attribute(self): + return False + + def is_relation(self): + return False + + def _thing_stream(self, method: concept_proto.Thing.Req, thing_list_getter: Callable[[concept_proto.Thing.Res], List[concept_proto.Thing]]): + method.iid = concept_proto_builder.iid(self.get_iid()) + request = transaction_proto.Transaction.Req() + request.thing_req.CopyFrom(method) + return map(lambda thing_proto: concept_proto_reader.thing(thing_proto), self._transaction._stream(request, lambda res: thing_list_getter(res.thing_res))) + + def _type_stream(self, method: concept_proto.Thing.Req, type_list_getter: Callable[[concept_proto.Thing.Res], List[concept_proto.Type]]): + method.iid = concept_proto_builder.iid(self.get_iid()) + request = transaction_proto.Transaction.Req() + request.thing_req.CopyFrom(method) + return map(lambda type_proto: concept_proto_reader.type_(type_proto), self._transaction._stream(request, lambda res: type_list_getter(res.thing_res))) + + def _execute(self, method: concept_proto.Thing.Req): + method.iid = concept_proto_builder.iid(self.get_iid()) + request = transaction_proto.Transaction.Req() + request.thing_req.CopyFrom(method) + return self._transaction._execute(request).thing_res + + def __str__(self): + return type(self).__name__ + "[iid:" + str(self._iid) + "]" + + def __eq__(self, other): + if other is self: + return True + if not other or type(self) != type(other): + return False + return self._transaction is other._transaction and self._iid == other._iid + + def __hash__(self): + return self._hash diff --git a/grakn/concept/type/attribute_type.py b/grakn/concept/type/attribute_type.py new file mode 100644 index 00000000..6f17af4e --- /dev/null +++ b/grakn/concept/type/attribute_type.py @@ -0,0 +1,303 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +from datetime import datetime + +import graknprotocol.protobuf.concept_pb2 as concept_proto + +from grakn.concept.proto import concept_proto_builder, concept_proto_reader +from grakn.concept.type.thing_type import ThingType, RemoteThingType +from grakn.concept.type.value_type import ValueType + + +def is_keyable(value_type: ValueType): + return value_type in [ValueType.LONG, ValueType.STRING, ValueType.DATETIME] + + +class AttributeType(ThingType): + + def as_remote(self, transaction): + return RemoteAttributeType(transaction, self.get_label(), self.is_root()) + + def get_value_type(self): + return ValueType.OBJECT + + def is_keyable(self): + return is_keyable(self.get_value_type()) + + def is_attribute_type(self): + return True + + def is_boolean(self): + return False + + def is_long(self): + return False + + def is_double(self): + return False + + def is_string(self): + return False + + def is_datetime(self): + return False + + def __eq__(self, other): + if other is self: + return True + # root "attribute" should always be equal to itself regardless of which value class it holds + if not other or not isinstance(AttributeType, other): + return False + return self.get_label() == other.get_label() + + def __hash__(self): + return super(AttributeType, self).__hash__() + + +class RemoteAttributeType(RemoteThingType): + + def get_value_type(self): + return ValueType.OBJECT + + def is_keyable(self): + return is_keyable(self.get_value_type()) + + def as_remote(self, transaction): + return RemoteAttributeType(transaction, self.get_label(), self.is_root()) + + def get_owners(self, only_key=False): + method = concept_proto.Type.Req() + get_owners_req = concept_proto.AttributeType.GetOwners.Req() + get_owners_req.only_key = only_key + method.attribute_type_get_owners_req.CopyFrom(get_owners_req) + return self._type_stream(method, lambda res: res.attribute_type_get_owners_res.owners) + + def _put_internal(self, value_proto): + method = concept_proto.Type.Req() + put_req = concept_proto.AttributeType.Put.Req() + put_req.value.CopyFrom(value_proto) + method.attribute_type_put_req.CopyFrom(put_req) + return concept_proto_reader.attribute(self._execute(method).attribute_type_put_res.attribute) + + def _get_internal(self, value_proto): + method = concept_proto.Type.Req() + get_req = concept_proto.AttributeType.Get.Req() + get_req.value.CopyFrom(value_proto) + method.attribute_type_get_req.CopyFrom(get_req) + response = self._execute(method).attribute_type_get_res + return concept_proto_reader.attribute(response.attribute) if response.WhichOneof("res") == "attribute" else None + + def is_attribute_type(self): + return True + + def is_boolean(self): + return False + + def is_long(self): + return False + + def is_double(self): + return False + + def is_string(self): + return False + + def is_datetime(self): + return False + + def __eq__(self, other): + if other is self: + return True + if not other or not isinstance(RemoteAttributeType, other): + return False + return self.get_label() == other.get_label() + + def __hash__(self): + return super(RemoteAttributeType, self).__hash__() + + +class BooleanAttributeType(AttributeType): + + @staticmethod + def _of(type_proto: concept_proto.Type): + return BooleanAttributeType(type_proto.label, type_proto.root) + + def get_value_type(self): + return ValueType.BOOLEAN + + def as_remote(self, transaction): + return RemoteBooleanAttributeType(transaction, self.get_label(), self.is_root()) + + def is_boolean(self): + return True + + +class RemoteBooleanAttributeType(RemoteAttributeType): + + def get_value_type(self): + return ValueType.BOOLEAN + + def as_remote(self, transaction): + return RemoteBooleanAttributeType(transaction, self.get_label(), self.is_root()) + + def put(self, value: bool): + return self._put_internal(concept_proto_builder.boolean_attribute_value(value)) + + def get(self, value: bool): + return self._get_internal(concept_proto_builder.boolean_attribute_value(value)) + + def is_boolean(self): + return True + + +class LongAttributeType(AttributeType): + + @staticmethod + def _of(type_proto: concept_proto.Type): + return LongAttributeType(type_proto.label, type_proto.root) + + def get_value_type(self): + return ValueType.LONG + + def as_remote(self, transaction): + return RemoteLongAttributeType(transaction, self.get_label(), self.is_root()) + + def is_long(self): + return True + + +class RemoteLongAttributeType(RemoteAttributeType): + + def get_value_type(self): + return ValueType.LONG + + def as_remote(self, transaction): + return RemoteLongAttributeType(transaction, self.get_label(), self.is_root()) + + def put(self, value: int): + return self._put_internal(concept_proto_builder.long_attribute_value(value)) + + def get(self, value: int): + return self._get_internal(concept_proto_builder.long_attribute_value(value)) + + def is_long(self): + return True + + +class DoubleAttributeType(AttributeType): + + @staticmethod + def _of(type_proto: concept_proto.Type): + return DoubleAttributeType(type_proto.label, type_proto.root) + + def get_value_type(self): + return ValueType.DOUBLE + + def as_remote(self, transaction): + return RemoteDoubleAttributeType(transaction, self.get_label(), self.is_root()) + + def is_double(self): + return True + + +class RemoteDoubleAttributeType(RemoteAttributeType): + + def get_value_type(self): + return ValueType.DOUBLE + + def as_remote(self, transaction): + return RemoteDoubleAttributeType(transaction, self.get_label(), self.is_root()) + + def put(self, value: float): + return self._put_internal(concept_proto_builder.double_attribute_value(value)) + + def get(self, value: float): + return self._get_internal(concept_proto_builder.double_attribute_value(value)) + + def is_double(self): + return True + + +class StringAttributeType(AttributeType): + + @staticmethod + def _of(type_proto: concept_proto.Type): + return StringAttributeType(type_proto.label, type_proto.root) + + def get_value_type(self): + return ValueType.STRING + + def as_remote(self, transaction): + return RemoteStringAttributeType(transaction, self.get_label(), self.is_root()) + + def is_string(self): + return True + + +class RemoteStringAttributeType(RemoteAttributeType): + + def get_value_type(self): + return ValueType.STRING + + def as_remote(self, transaction): + return RemoteStringAttributeType(transaction, self.get_label(), self.is_root()) + + def put(self, value: str): + return self._put_internal(concept_proto_builder.string_attribute_value(value)) + + def get(self, value: str): + return self._get_internal(concept_proto_builder.string_attribute_value(value)) + + def is_string(self): + return True + + +class DateTimeAttributeType(AttributeType): + + @staticmethod + def _of(type_proto: concept_proto.Type): + return DateTimeAttributeType(type_proto.label, type_proto.root) + + def get_value_type(self): + return ValueType.DATETIME + + def as_remote(self, transaction): + return RemoteDateTimeAttributeType(transaction, self.get_label(), self.is_root()) + + def is_datetime(self): + return True + + +class RemoteDateTimeAttributeType(RemoteAttributeType): + + def get_value_type(self): + return ValueType.DATETIME + + def as_remote(self, transaction): + return RemoteDateTimeAttributeType(transaction, self.get_label(), self.is_root()) + + def put(self, value: datetime): + return self._put_internal(concept_proto_builder.datetime_attribute_value(value)) + + def get(self, value: datetime): + return self._get_internal(concept_proto_builder.datetime_attribute_value(value)) + + def is_datetime(self): + return True diff --git a/grakn/concept/type/entity_type.py b/grakn/concept/type/entity_type.py new file mode 100644 index 00000000..77de00de --- /dev/null +++ b/grakn/concept/type/entity_type.py @@ -0,0 +1,51 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +import graknprotocol.protobuf.concept_pb2 as concept_proto + +from grakn.concept.thing.entity import Entity +from grakn.concept.type.thing_type import ThingType, RemoteThingType + + +class EntityType(ThingType): + + @staticmethod + def _of(type_proto: concept_proto.Type): + return EntityType(type_proto.label, type_proto.root) + + def as_remote(self, transaction): + return RemoteEntityType(transaction, self.get_label(), self.is_root()) + + def is_entity_type(self): + return True + + +class RemoteEntityType(RemoteThingType): + + def as_remote(self, transaction): + return RemoteEntityType(transaction, self.get_label(), self.is_root()) + + def create(self): + method = concept_proto.Type.Req() + create_req = concept_proto.EntityType.Create.Req() + method.entity_type_create_req.CopyFrom(create_req) + return Entity._of(self._execute(method).entity_type_create_res.entity) + + def is_entity_type(self): + return True diff --git a/grakn/concept/type/relation_type.py b/grakn/concept/type/relation_type.py new file mode 100644 index 00000000..b4115d91 --- /dev/null +++ b/grakn/concept/type/relation_type.py @@ -0,0 +1,77 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +import graknprotocol.protobuf.concept_pb2 as concept_proto + +from grakn.concept.proto import concept_proto_reader +from grakn.concept.thing.relation import Relation +from grakn.concept.type.thing_type import ThingType, RemoteThingType + + +class RelationType(ThingType): + + @staticmethod + def _of(type_proto: concept_proto.Type): + return RelationType(type_proto.label, type_proto.root) + + def as_remote(self, transaction): + return RemoteRelationType(transaction, self.get_label(), self.is_root()) + + def is_relation_type(self): + return True + + +class RemoteRelationType(RemoteThingType): + + def as_remote(self, transaction): + return RemoteRelationType(transaction, self.get_label(), self.is_root()) + + def create(self): + method = concept_proto.Type.Req() + create_req = concept_proto.RelationType.Create.Req() + method.relation_type_create_req.CopyFrom(create_req) + return Relation._of(self._execute(method).relation_type_create_res.relation) + + def get_relates(self, role_label: str = None): + method = concept_proto.Type.Req() + if role_label: + get_relates_req = concept_proto.RelationType.GetRelatesForRoleLabel.Req() + get_relates_req.label = role_label + method.relation_type_get_relates_for_role_label_req.CopyFrom(get_relates_req) + res = self._execute(method).relation_type_get_relates_for_role_label_res + return concept_proto_reader.type_(res.role_type) if res.HasField("role_type") else None + else: + method.relation_type_get_relates_req.CopyFrom(concept_proto.RelationType.GetRelates.Req()) + return self._type_stream(method, lambda res: res.relation_type_get_relates_res.roles) + + def set_relates(self, role_label: str, overridden_label: str = None): + method = concept_proto.Type.Req() + set_relates_req = concept_proto.RelationType.SetRelates.Req() + set_relates_req.label = role_label + if overridden_label: + set_relates_req.overridden_label = overridden_label + method.relation_type_set_relates_req.CopyFrom(set_relates_req) + self._execute(method) + + def unset_relates(self, role_label: str): + method = concept_proto.Type.Req() + unset_relates_req = concept_proto.RelationType.UnsetRelates.Req() + unset_relates_req.label = role_label + method.relation_type_unset_relates_req.CopyFrom(unset_relates_req) + self._execute(method) diff --git a/grakn/concept/type/role_type.py b/grakn/concept/type/role_type.py new file mode 100644 index 00000000..e6b5e12d --- /dev/null +++ b/grakn/concept/type/role_type.py @@ -0,0 +1,118 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +from typing import Callable, List + +import graknprotocol.protobuf.concept_pb2 as concept_proto + +from grakn.concept.proto import concept_proto_reader +from grakn.concept.type.type import Type, RemoteType + + +class RoleType(Type): + + def __init__(self, label: str, scope: str, is_root: bool): + super(RoleType, self).__init__(label, is_root) + self._scope = scope + self._hash = hash((scope, label)) + + @staticmethod + def _of(type_proto: concept_proto.Type): + return RoleType(type_proto.label, type_proto.scope, type_proto.root) + + def get_scope(self): + return self._scope + + def get_scoped_label(self): + return self.get_scope() + ":" + self.get_label() + + def as_remote(self, transaction): + return RemoteRoleType(transaction, self.get_label(), self.get_scope(), self.is_root()) + + def is_role_type(self): + return True + + def __str__(self): + return type(self).__name__ + "[label:" + self.get_scoped_label() + "]" + + def __eq__(self, other): + if other is self: + return True + if not other or type(self) != type(other): + return False + return self.get_scoped_label() == other.get_scoped_label() + + def __hash__(self): + return super(RoleType, self).__hash__() + + +class RemoteRoleType(RemoteType): + + def __init__(self, transaction, label: str, scope: str, is_root: bool): + super(RemoteRoleType, self).__init__(transaction, label, is_root) + self._scope = scope + self._hash = hash((transaction, scope, label)) + + def get_scope(self): + return self._scope + + def get_scoped_label(self): + return self.get_scope() + ":" + self.get_label() + + def as_remote(self, transaction): + return RemoteRoleType(transaction, self.get_label(), self.get_scope(), self.is_root()) + + def get_relation_type(self): + method = concept_proto.Type.Req() + method.role_type_get_relation_type_req.CopyFrom(concept_proto.RoleType.GetRelationType.Req()) + return concept_proto_reader.type_(self._execute(method).role_type_get_relation_type_res.relation_type) + + def get_relation_types(self): + method = concept_proto.Type.Req() + method.role_type_get_relation_types_req.CopyFrom(concept_proto.RoleType.GetRelationTypes.Req()) + return self._type_stream(method, lambda res: res.role_type_get_relation_types_res.relation_types) + + def get_players(self): + method = concept_proto.Type.Req() + method.role_type_get_players_req.CopyFrom(concept_proto.RoleType.GetPlayers.Req()) + return self._type_stream(method, lambda res: res.role_type_get_players_res.thing_types) + + def is_role_type(self): + return True + + def _type_stream(self, method: concept_proto.Type.Req, type_list_getter: Callable[[concept_proto.Type.Res], List[concept_proto.Type]]): + method.scope = self.get_scope() + return super(RemoteRoleType, self)._type_stream(method, type_list_getter) + + def _execute(self, method: concept_proto.Type.Req): + method.scope = self.get_scope() + return super(RemoteRoleType, self)._execute(method) + + def __str__(self): + return type(self).__name__ + "[label:" + self.get_scoped_label() + "]" + + def __eq__(self, other): + if other is self: + return True + if not other or type(self) != type(other): + return False + return self.get_scoped_label() == other.get_scoped_label() + + def __hash__(self): + return super(RemoteRoleType, self).__hash__() diff --git a/grakn/concept/type/thing_type.py b/grakn/concept/type/thing_type.py new file mode 100644 index 00000000..87608265 --- /dev/null +++ b/grakn/concept/type/thing_type.py @@ -0,0 +1,101 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +import graknprotocol.protobuf.concept_pb2 as concept_proto + +from grakn.concept.proto import concept_proto_builder +from grakn.concept.type.type import Type, RemoteType + + +class ThingType(Type): + + def as_remote(self, transaction): + return RemoteThingType(transaction, self.get_label(), self.is_root()) + + +class RemoteThingType(RemoteType): + + def get_instances(self): + method = concept_proto.Type.Req() + get_instances_req = concept_proto.ThingType.GetInstances.Req() + method.thing_type_get_instances_req.CopyFrom(get_instances_req) + return self._thing_stream(method, lambda res: res.thing_type_get_instances_res.things) + + def set_abstract(self): + req = concept_proto.Type.Req() + req.thing_type_set_abstract_req.CopyFrom(concept_proto.ThingType.SetAbstract.Req()) + self._execute(req) + + def unset_abstract(self): + req = concept_proto.Type.Req() + req.thing_type_unset_abstract_req.CopyFrom(concept_proto.ThingType.UnsetAbstract.Req()) + self._execute(req) + + def set_plays(self, role, overridden_role=None): + req = concept_proto.Type.Req() + set_plays_req = concept_proto.ThingType.SetPlays.Req() + set_plays_req.role.CopyFrom(concept_proto_builder.type_(role)) + if overridden_role: + set_plays_req.overridden_role.CopyFrom(concept_proto_builder.type_(overridden_role)) + req.thing_type_set_plays_req.CopyFrom(set_plays_req) + self._execute(req) + + def set_owns(self, attribute_type, overridden_type=None, is_key=False): + req = concept_proto.Type.Req() + set_owns_req = concept_proto.ThingType.SetOwns.Req() + set_owns_req.attribute_type.CopyFrom(concept_proto_builder.type_(attribute_type)) + set_owns_req.is_key = is_key + if overridden_type: + set_owns_req.overridden_type.CopyFrom(concept_proto_builder.type_(overridden_type)) + req.thing_type_set_owns_req.CopyFrom(set_owns_req) + self._execute(req) + + def get_plays(self): + req = concept_proto.Type.Req() + req.thing_type_get_plays_req.CopyFrom(concept_proto.ThingType.GetPlays.Req()) + return self._type_stream(req, lambda res: res.thing_type_get_plays_res.roles) + + def get_owns(self, value_type=None, keys_only=False): + req = concept_proto.Type.Req() + get_owns_req = concept_proto.ThingType.GetOwns.Req() + get_owns_req.keys_only = keys_only + if value_type: + get_owns_req.value_type = concept_proto_builder.value_type(value_type) + req.thing_type_get_owns_req.CopyFrom(get_owns_req) + return self._type_stream(req, lambda res: res.thing_type_get_owns_res.attribute_types) + + def unset_plays(self, role): + req = concept_proto.Type.Req() + unset_plays_req = concept_proto.ThingType.UnsetPlays.Req() + unset_plays_req.role.CopyFrom(concept_proto_builder.type_(role)) + req.thing_type_unset_plays_req.CopyFrom(unset_plays_req) + self._execute(req) + + def unset_owns(self, attribute_type): + req = concept_proto.Type.Req() + unset_owns_req = concept_proto.ThingType.UnsetOwns.Req() + unset_owns_req.attribute_type.CopyFrom(concept_proto_builder.type_(attribute_type)) + req.thing_type_unset_owns_req.CopyFrom(unset_owns_req) + self._execute(req) + + def as_remote(self, transaction): + return RemoteThingType(transaction, self.get_label(), self.is_root()) + + def is_thing_type(self): + return True diff --git a/grakn/concept/type/type.py b/grakn/concept/type/type.py new file mode 100644 index 00000000..5d1f9900 --- /dev/null +++ b/grakn/concept/type/type.py @@ -0,0 +1,188 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +from typing import Callable, List + +import graknprotocol.protobuf.concept_pb2 as concept_proto +import graknprotocol.protobuf.transaction_pb2 as transaction_proto + +from grakn.common.exception import GraknClientException +from grakn.concept.proto import concept_proto_builder, concept_proto_reader +from grakn.concept.concept import Concept, RemoteConcept + + +class Type(Concept): + + def __init__(self, label: str, is_root: bool): + if not label: + raise GraknClientException("Label must be a non-empty string.") + self._label = label + self._is_root = is_root + self._hash = hash(label) + + def get_label(self): + return self._label + + def is_root(self): + return self._is_root + + def is_type(self): + return True + + def is_thing_type(self): + return False + + def is_entity_type(self): + return False + + def is_attribute_type(self): + return False + + def is_relation_type(self): + return False + + def is_role_type(self): + return False + + def __str__(self): + return type(self).__name__ + "[label:" + self.get_label() + "]" + + def __eq__(self, other): + if other is self: + return True + if not other or type(self) != type(other): + return False + return self.get_label() == other.get_label() + + def __hash__(self): + return self._hash + + +class RemoteType(RemoteConcept): + + def __init__(self, transaction, label: str, is_root: bool): + if not transaction: + raise GraknClientException("Transaction must be set.") + if not label: + raise GraknClientException("Label must be a non-empty string.") + self._transaction = transaction + self._label = label + self._is_root = is_root + self._hash = hash((self._transaction, label)) + + def get_label(self): + return self._label + + def is_root(self): + return self._is_root + + def set_label(self, label: str): + req = concept_proto.Type.Req() + set_label_req = concept_proto.Type.SetLabel.Req() + set_label_req.label = label + req.type_set_label_req.CopyFrom(set_label_req) + self._execute(req) + self._label = label + self._hash = hash((self._transaction, label)) + + def is_abstract(self): + req = concept_proto.Type.Req() + req.type_is_abstract_req.CopyFrom(concept_proto.Type.IsAbstract.Req()) + res = self._execute(req) + return res.type_is_abstract_res.abstract + + def is_type(self): + return True + + def is_thing_type(self): + return False + + def is_entity_type(self): + return False + + def is_attribute_type(self): + return False + + def is_relation_type(self): + return False + + def is_role_type(self): + return False + + def set_supertype(self, _type: Type): + req = concept_proto.Type.Req() + supertype_req = concept_proto.Type.SetSupertype.Req() + supertype_req.type.CopyFrom(concept_proto_builder.type_(_type)) + req.type_set_supertype_req.CopyFrom(supertype_req) + self._execute(req) + + def get_supertype(self): + req = concept_proto.Type.Req() + req.type_get_supertype_req.CopyFrom(concept_proto.Type.GetSupertype.Req()) + res = self._execute(req).type_get_supertype_res + return concept_proto_reader.type_(res.type) if res.WhichOneof("res") == "type" else None + + def get_supertypes(self): + method = concept_proto.Type.Req() + method.type_get_supertypes_req.CopyFrom(concept_proto.Type.GetSupertypes.Req()) + return self._type_stream(method, lambda res: res.type_get_supertypes_res.types) + + def get_subtypes(self): + method = concept_proto.Type.Req() + method.type_get_subtypes_req.CopyFrom(concept_proto.Type.GetSubtypes.Req()) + return self._type_stream(method, lambda res: res.type_get_subtypes_res.types) + + def delete(self): + method = concept_proto.Type.Req() + method.type_delete_req.CopyFrom(concept_proto.Type.Delete.Req()) + self._execute(method) + + def is_deleted(self): + return not self._transaction.concepts().get_type(self.get_label()) + + def _type_stream(self, method: concept_proto.Type.Req, type_list_getter: Callable[[concept_proto.Type.Res], List[concept_proto.Type]]): + method.label = self.get_label() + request = transaction_proto.Transaction.Req() + request.type_req.CopyFrom(method) + return map(lambda type_proto: concept_proto_reader.type_(type_proto), self._transaction._stream(request, lambda res: type_list_getter(res.type_res))) + + def _thing_stream(self, method: concept_proto.Type.Req, thing_list_getter: Callable[[concept_proto.Type.Res], List[concept_proto.Thing]]): + method.label = self.get_label() + request = transaction_proto.Transaction.Req() + request.type_req.CopyFrom(method) + return map(lambda thing_proto: concept_proto_reader.thing(thing_proto), self._transaction._stream(request, lambda res: thing_list_getter(res.type_res))) + + def _execute(self, method: concept_proto.Type.Req): + method.label = self.get_label() + request = transaction_proto.Transaction.Req() + request.type_req.CopyFrom(method) + return self._transaction._execute(request).type_res + + def __str__(self): + return type(self).__name__ + "[label:" + self.get_label() + "]" + + def __eq__(self, other): + if other is self: + return True + if not other or type(self) != type(other): + return False + return self._transaction is other._transaction and self.get_label() == other.get_label() + + def __hash__(self): + return self._hash diff --git a/grakn/concept/type/value_type.py b/grakn/concept/type/value_type.py new file mode 100644 index 00000000..e6ad39f9 --- /dev/null +++ b/grakn/concept/type/value_type.py @@ -0,0 +1,30 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +import enum + + +# This lives here to avoid circular imports. +class ValueType(enum.Enum): + OBJECT = 0 + BOOLEAN = 1 + LONG = 2 + DOUBLE = 3 + STRING = 4 + DATETIME = 5 diff --git a/grakn/grakn_proto_builder.py b/grakn/grakn_proto_builder.py new file mode 100644 index 00000000..cec9e89c --- /dev/null +++ b/grakn/grakn_proto_builder.py @@ -0,0 +1,33 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +import graknprotocol.protobuf.options_pb2 as options_proto + +from grakn.options import GraknOptions + + +def options(opts: GraknOptions): + proto_options = options_proto.Options() + if opts.infer is not None: + proto_options.infer = opts.infer + if opts.explain is not None: + proto_options.explain = opts.explain + if opts.batch_size is not None: + proto_options.batch_size = opts.batch_size + return proto_options diff --git a/grakn/logic/logic_manager.py b/grakn/logic/logic_manager.py new file mode 100644 index 00000000..fa832c6d --- /dev/null +++ b/grakn/logic/logic_manager.py @@ -0,0 +1,53 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +import graknprotocol.protobuf.logic_pb2 as logic_proto +import graknprotocol.protobuf.transaction_pb2 as transaction_proto + +from grakn.logic.rule import Rule + + +class LogicManager(object): + + def __init__(self, transaction): + self._transaction = transaction + + def put_rule(self, label: str, when: str, then: str): + req = logic_proto.LogicManager.Req() + put_rule_req = logic_proto.LogicManager.PutRule.Req() + put_rule_req.label = label + put_rule_req.when = when + put_rule_req.then = then + req.put_rule_req.CopyFrom(put_rule_req) + res = self._execute(req) + return Rule._of(res.put_rule_res.rule) + + def get_rule(self, label: str): + req = logic_proto.LogicManager.Req() + get_rule_req = logic_proto.LogicManager.GetRule.Req() + get_rule_req.label = label + req.get_rule_req.CopyFrom(get_rule_req) + + response = self._execute(req) + return Rule._of(response.get_rule_res.rule) if response.get_rule_res.WhichOneof("res") == "rule" else None + + def _execute(self, request: logic_proto.LogicManager.Req): + req = transaction_proto.Transaction.Req() + req.logic_manager_req.CopyFrom(request) + return self._transaction._execute(req).logic_manager_res diff --git a/grakn/logic/rule.py b/grakn/logic/rule.py new file mode 100644 index 00000000..42d561d1 --- /dev/null +++ b/grakn/logic/rule.py @@ -0,0 +1,111 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +import graknprotocol.protobuf.logic_pb2 as logic_proto +import graknprotocol.protobuf.transaction_pb2 as transaction_proto + +from grakn.common.exception import GraknClientException + + +class Rule(object): + + def __init__(self, label: str, when: str, then: str): + if not label: + raise GraknClientException("Label must be a non-empty string.") + self._label = label + self._when = when + self._then = then + self._hash = hash(label) + + @staticmethod + def _of(rule_proto: logic_proto.Rule): + return Rule(rule_proto.label, rule_proto.when, rule_proto.then) + + def get_label(self): + return self._label + + def get_when(self): + return self._when + + def get_then(self): + return self._then + + def as_remote(self, transaction): + return RemoteRule(transaction, self.get_label(), self.get_when(), self.get_then()) + + def is_remote(self): + return False + + def __str__(self): + return type(self).__name__ + "[label:" + self.get_label() + "]" + + def __eq__(self, other): + if other is self: + return True + if not other or type(self) != type(other): + return False + return self.get_label() == other.get_label() + + def __hash__(self): + return self._hash + + +class RemoteRule(Rule): + + def __init__(self, transaction, label: str, when: str, then: str): + super(RemoteRule, self).__init__(label, when, then) + if not transaction: + raise GraknClientException("Transaction must be set.") + self._transaction = transaction + self._hash = hash((transaction, label)) + + def set_label(self, label: str): + req = logic_proto.Rule.Req() + set_label_req = logic_proto.Rule.SetLabel.Req() + set_label_req.label = label + req.rule_set_label_req.CopyFrom(set_label_req) + self._execute(req) + self._label = label + + def delete(self): + method = logic_proto.Rule.Req() + method.rule_delete_req.CopyFrom(logic_proto.Rule.Delete.Req()) + self._execute(method) + + def is_deleted(self): + return not self._transaction.logic().get_rule(self.get_label()) + + def is_remote(self): + return True + + def __eq__(self, other): + if other is self: + return True + if not other or type(self) != type(other): + return False + return self._transaction is other._transaction and self.get_label() == other.get_label() + + def __hash__(self): + return super(RemoteRule, self).__hash__() + + def _execute(self, method: logic_proto.Rule.Req): + method.label = self.get_label() + request = transaction_proto.Transaction.Req() + request.rule_req.CopyFrom(method) + return self._transaction._execute(request).rule_res diff --git a/grakn/options.py b/grakn/options.py new file mode 100644 index 00000000..4b6229ae --- /dev/null +++ b/grakn/options.py @@ -0,0 +1,26 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + + +class GraknOptions(object): + + def __init__(self): + self.infer = None + self.explain = None + self.batch_size = None diff --git a/grakn/query/query_manager.py b/grakn/query/query_manager.py new file mode 100644 index 00000000..0428fdd6 --- /dev/null +++ b/grakn/query/query_manager.py @@ -0,0 +1,81 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +from typing import Callable, List + +import graknprotocol.protobuf.query_pb2 as query_proto +import graknprotocol.protobuf.transaction_pb2 as transaction_proto + +from grakn import grakn_proto_builder +from grakn.concept.answer import concept_map +from grakn.options import GraknOptions + + +class QueryManager(object): + + def __init__(self, transaction): + self._transaction = transaction + + def match(self, query: str, options=GraknOptions()): + request = query_proto.Query.Req() + match_req = query_proto.Graql.Match.Req() + match_req.query = query + request.match_req.CopyFrom(match_req) + return map(lambda answer_proto: concept_map._of(answer_proto), self._iterate_query(request, lambda res: res.query_res.match_res.answers, options)) + + def insert(self, query: str, options=GraknOptions()): + request = query_proto.Query.Req() + insert_req = query_proto.Graql.Insert.Req() + insert_req.query = query + request.insert_req.CopyFrom(insert_req) + return map(lambda answer_proto: concept_map._of(answer_proto), self._iterate_query(request, lambda res: res.query_res.insert_res.answers, options)) + + def delete(self, query: str, options=GraknOptions()): + request = query_proto.Query.Req() + delete_req = query_proto.Graql.Delete.Req() + delete_req.query = query + request.delete_req.CopyFrom(delete_req) + self._run_query(request, options) + + def define(self, query: str, options=GraknOptions()): + request = query_proto.Query.Req() + define_req = query_proto.Graql.Define.Req() + define_req.query = query + request.define_req.CopyFrom(define_req) + self._run_query(request, options) + + def undefine(self, query: str, options=GraknOptions()): + request = query_proto.Query.Req() + undefine_req = query_proto.Graql.Undefine.Req() + undefine_req.query = query + request.undefine_req.CopyFrom(undefine_req) + self._run_query(request, options) + + def _run_query(self, query_req: query_proto.Query.Req, options: GraknOptions): + req = transaction_proto.Transaction.Req() + query_req.options.CopyFrom(grakn_proto_builder.options(options)) + req.query_req.CopyFrom(query_req) + # Using stream makes this request asynchronous. + self._transaction._stream(req) + + def _iterate_query(self, query_req: query_proto.Query.Req, response_reader: Callable[[transaction_proto.Transaction.Res], List], options: GraknOptions): + req = transaction_proto.Transaction.Req() + query_req.options.CopyFrom(grakn_proto_builder.options(options)) + req.query_req.CopyFrom(query_req) + return self._transaction._stream(req, response_reader) diff --git a/grakn/rpc/Transaction.py b/grakn/rpc/Transaction.py new file mode 100644 index 00000000..63b634b3 --- /dev/null +++ b/grakn/rpc/Transaction.py @@ -0,0 +1,194 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +import enum +from typing import Callable, List + +import grpc +import six +import time +import uuid + +from six.moves import queue + +from graknprotocol.protobuf.grakn_pb2_grpc import GraknStub +import graknprotocol.protobuf.transaction_pb2 as transaction_proto + +from grakn import grakn_proto_builder +from grakn.common.exception import GraknClientException +from grakn.concept.concept_manager import ConceptManager +from grakn.options import GraknOptions +from grakn.query.query_manager import QueryManager +from grakn.rpc.stream import Stream +from grakn.logic.logic_manager import LogicManager + + +class TransactionType(enum.Enum): + READ = 0 + WRITE = 1 + + +class Transaction(object): + + def __init__(self, channel: grpc.Channel, session_id: str, transaction_type: TransactionType, options=GraknOptions()): + self._transaction_type = transaction_type + self._concept_manager = ConceptManager(self) + self._query_manager = QueryManager(self) + self._logic_manager = LogicManager(self) + self._response_queues = {} + + self._grpc_stub = GraknStub(channel) + self._request_iterator = RequestIterator() + self._response_iterator = self._grpc_stub.transaction(self._request_iterator) + self._transaction_was_closed = False + + open_req = transaction_proto.Transaction.Open.Req() + open_req.session_id = session_id + open_req.type = Transaction._transaction_type_proto(transaction_type) + open_req.options.CopyFrom(grakn_proto_builder.options(options)) + req = transaction_proto.Transaction.Req() + req.open_req.CopyFrom(open_req) + + start_time = time.time() * 1000.0 + res = self._execute(req) + end_time = time.time() * 1000.0 + self._network_latency_millis = end_time - start_time - res.open_res.processing_time_millis + + def transaction_type(self): + return self._transaction_type + + def is_open(self): + return not self._transaction_was_closed + + def concepts(self): + return self._concept_manager + + def query(self): + return self._query_manager + + def logic(self): + return self._logic_manager + + def commit(self): + req = transaction_proto.Transaction.Req() + commit_req = transaction_proto.Transaction.Commit.Req() + req.commit_req.CopyFrom(commit_req) + self._execute(req) + + def rollback(self): + req = transaction_proto.Transaction.Req() + rollback_req = transaction_proto.Transaction.Rollback.Req() + req.rollback_req.CopyFrom(rollback_req) + self._execute(req) + + def close(self): + self._transaction_was_closed = True + self._request_iterator.close() + + def _execute(self, request: transaction_proto.Transaction.Req): + response_queue = queue.Queue() + request_id = str(uuid.uuid4()) + request.id = request_id + if self._transaction_was_closed: + raise GraknClientException("The transaction has been closed and no further operation is allowed.") + self._response_queues[request_id] = response_queue + self._request_iterator.put(request) + return self._fetch(request_id) + + def _stream(self, request: transaction_proto.Transaction.Req, transform_response: Callable[[transaction_proto.Transaction.Res], List] = None): + response_queue = queue.Queue() + request_id = str(uuid.uuid4()) + request.id = request_id + if self._transaction_was_closed: + raise GraknClientException("The transaction has been closed and no further operation is allowed.") + self._response_queues[request_id] = response_queue + self._request_iterator.put(request) + return Stream(self, request_id, transform_response) + + def _fetch(self, request_id: str): + try: + return self._response_queues[request_id].get(block=False) + except queue.Empty: + pass + + # Keep taking responses until we get one that matches the request ID + while True: + try: + response = next(self._response_iterator) + except grpc.RpcError as e: + self._transaction_was_closed = True + grakn_exception = GraknClientException(e.details()) + for response_queue in self._response_queues.values(): + response_queue.put(grakn_exception) + # noinspection PyUnresolvedReferences + raise grakn_exception + except StopIteration: + raise GraknClientException("The transaction has been closed and no further operation is allowed.") + + if isinstance(response, GraknClientException): + raise response + elif response.id == request_id: + return response + else: + response_queue = self._response_queues[response.id] + if response_queue is None: + raise GraknClientException("Received a response with unknown request id '" + response.id + "'.") + response_queue.put(response) + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + self.close() + if exc_tb is None: + pass + else: + return False + + @staticmethod + def _transaction_type_proto(transaction_type): + if transaction_type == TransactionType.READ: + return transaction_proto.Transaction.Type.Value("READ") + if transaction_type == TransactionType.WRITE: + return transaction_proto.Transaction.Type.Value("WRITE") + + +class RequestIterator(six.Iterator): + CLOSE_STREAM = "CLOSE_STREAM" + + def __init__(self): + self._request_queue = queue.Queue() + + def __iter__(self): + return self + + # Essentially the gRPC stream is constantly polling this iterator. When we issue a new request, it gets put into + # the back of the queue and gRPC will pick it up when it gets round to it (this is usually instantaneous) + def __next__(self): + request = self._request_queue.get(block=True) + if request is RequestIterator.CLOSE_STREAM: + # Close the stream. + raise StopIteration() + return request + + def put(self, request: transaction_proto.Transaction.Req): + self._request_queue.put(request) + + def close(self): + self._request_queue.put(RequestIterator.CLOSE_STREAM) diff --git a/grakn/rpc/database_manager.py b/grakn/rpc/database_manager.py new file mode 100644 index 00000000..45ec2a6c --- /dev/null +++ b/grakn/rpc/database_manager.py @@ -0,0 +1,46 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +from graknprotocol.protobuf.grakn_pb2_grpc import GraknStub +import graknprotocol.protobuf.database_pb2 as database_proto +from grpc import Channel + + +class DatabaseManager(object): + + def __init__(self, channel: Channel): + self._grpc_stub = GraknStub(channel) + + def contains(self, name: str): + request = database_proto.Database.Contains.Req() + request.name = name + return self._grpc_stub.database_contains(request).contains + + def create(self, name: str): + request = database_proto.Database.Create.Req() + request.name = name + self._grpc_stub.database_create(request) + + def delete(self, name: str): + request = database_proto.Database.Delete.Req() + request.name = name + self._grpc_stub.database_delete(request) + + def all(self): + return list(self._grpc_stub.database_all(database_proto.Database.All.Req()).names) diff --git a/grakn/rpc/session.py b/grakn/rpc/session.py new file mode 100644 index 00000000..6ee13efa --- /dev/null +++ b/grakn/rpc/session.py @@ -0,0 +1,92 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +from graknprotocol.protobuf.grakn_pb2_grpc import GraknStub +import graknprotocol.protobuf.session_pb2 as session_proto +import enum + +from grakn import grakn_proto_builder +from grakn.options import GraknOptions +from grakn.rpc.transaction import Transaction, TransactionType + + +class SessionType(enum.Enum): + DATA = 0 + SCHEMA = 1 + + +class Session(object): + + def __init__(self, client, database: str, session_type: SessionType, options=GraknOptions()): + self._channel = client._channel + self._scheduler = client._scheduler + self._database = database + self._session_type = session_type + self._grpc_stub = GraknStub(self._channel) + + open_req = session_proto.Session.Open.Req() + open_req.database = database + open_req.type = Session._session_type_proto(session_type) + open_req.options.CopyFrom(grakn_proto_builder.options(options)) + + self._session_id = self._grpc_stub.session_open(open_req).session_id + self._is_open = True + self._pulse = self._scheduler.enter(5, 1, self._transmit_pulse, ()) + + def transaction(self, transaction_type: TransactionType, options=GraknOptions()): + return Transaction(self._channel, self._session_id, transaction_type, options) + + def session_type(self): return self._session_type + + def is_open(self): return self._is_open + + def close(self): + if self._is_open: + self._is_open = False + req = session_proto.Session.Close.Req() + req.session_id = self._session_id + self._grpc_stub.session_close(req) + + def database(self): return self._database + + def _transmit_pulse(self): + if not self._is_open: + return + pulse_req = session_proto.Session.Pulse.Req() + pulse_req.session_id = self._session_id + is_alive = self._grpc_stub.session_pulse(pulse_req).is_alive + if is_alive: + self._pulse = self._scheduler.enter(5, 1, self._transmit_pulse, ()) + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + self.close() + if exc_tb is None: + pass + else: + return False + + @staticmethod + def _session_type_proto(session_type: SessionType): + if session_type == SessionType.DATA: + return session_proto.Session.Type.Value("DATA") + if session_type == SessionType.SCHEMA: + return session_proto.Session.Type.Value("SCHEMA") diff --git a/grakn/rpc/stream.py b/grakn/rpc/stream.py new file mode 100644 index 00000000..0fb214c4 --- /dev/null +++ b/grakn/rpc/stream.py @@ -0,0 +1,64 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +from typing import Callable, List + +import six + +import graknprotocol.protobuf.transaction_pb2 as transaction_proto + +from grakn.common.exception import GraknClientException + + +class Stream(six.Iterator): + + _CONTINUE = "continue" + _DONE = "done" + + def __init__(self, transaction, request_id: str, transform_response: Callable[[transaction_proto.Transaction.Res], List] = None): + self._transaction = transaction + self._request_id = request_id + self._transform_response = transform_response + self._current_iterator = None + + def __iter__(self): + return self + + def __next__(self): + if self._current_iterator is not None: + try: + return next(self._current_iterator) + except StopIteration: + self._current_iterator = None + + res = self._transaction._fetch(self._request_id) + res_case = res.WhichOneof("res") + if res_case == Stream._CONTINUE: + continue_req = transaction_proto.Transaction.Req() + continue_req.id = self._request_id + setattr(continue_req, "continue", True) + self._transaction._request_iterator.put(continue_req) + return next(self) + elif res_case == Stream._DONE: + raise StopIteration() + elif res_case is None: + raise GraknClientException("The required field 'res' of type 'transaction_proto.Transaction.Res' was not set.") + else: + self._current_iterator = iter(self._transform_response(res)) + return next(self) diff --git a/grakn/service/Keyspace/KeyspaceService.py b/grakn/service/Keyspace/KeyspaceService.py deleted file mode 100644 index 57cd9398..00000000 --- a/grakn/service/Keyspace/KeyspaceService.py +++ /dev/null @@ -1,46 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -from grakn_protocol.keyspace.Keyspace_pb2_grpc import KeyspaceServiceStub -import grakn_protocol.keyspace.Keyspace_pb2 as keyspace_messages - - -class KeyspaceService(object): - - def __init__(self, uri, channel, credentials=None): - self.uri = uri - self.stub = KeyspaceServiceStub(channel) - self.credentials = credentials - - def retrieve(self): - retrieve_request = keyspace_messages.Keyspace.Retrieve.Req() - if self.credentials: - retrieve_request.username = self.credentials['username'] - retrieve_request.password = self.credentials['password'] - response = self.stub.retrieve(retrieve_request) - return list(response.names) - - def delete(self, keyspace): - delete_request = keyspace_messages.Keyspace.Delete.Req() - delete_request.name = keyspace - if self.credentials: - delete_request.username = self.credentials['username'] - delete_request.password = self.credentials['password'] - self.stub.delete(delete_request) - return diff --git a/grakn/service/Session/Concept/BaseTypeMapping.py b/grakn/service/Session/Concept/BaseTypeMapping.py deleted file mode 100644 index a496e117..00000000 --- a/grakn/service/Session/Concept/BaseTypeMapping.py +++ /dev/null @@ -1,56 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -import grakn_protocol.session.Concept_pb2 as ConceptMessages - - -# base type constant names -CONCEPTS = META_TYPE, ATTRIBUTE_TYPE, RELATION_TYPE, ENTITY_TYPE, ENTITY, ATTRIBUTE, RELATION, ROLE, RULE = \ - "META_TYPE", "ATTRIBUTE_TYPE", "RELATION_TYPE", "ENTITY_TYPE", "ENTITY", "ATTRIBUTE", "RELATION", "ROLE", "RULE" - -""" -NOTE: the string META_TYPE is the name of the programmatic type of -Thing, Entity, Attribute, Relation IN GRPC. In the original Java server implementation, -these have the type TYPE, but due to bad naming at some point, GRPC -says the base_type of them is META_TYPE. Thus, there will never be -any concepts with base_type TYPE, only META_TYPE on GRPC-connected clients. -To match the server class hierarchy, I here instantiate TYPE objects rather than -META_TYPE, and when the time comes we will rename META_TYPE to TYPE on GRPC connected -clients too. -""" - -grpc_base_types = ConceptMessages.Concept.BASE_TYPE -grpc_base_type_to_name = { - grpc_base_types.Value("META_TYPE"): META_TYPE, - grpc_base_types.Value("ENTITY_TYPE"): ENTITY_TYPE, - grpc_base_types.Value("RELATION_TYPE"): RELATION_TYPE, - grpc_base_types.Value("ATTRIBUTE_TYPE"): ATTRIBUTE_TYPE, - grpc_base_types.Value("ROLE"): ROLE, - grpc_base_types.Value("RULE"): RULE, - grpc_base_types.Value("ENTITY"): ENTITY, - grpc_base_types.Value("RELATION"): RELATION, - grpc_base_types.Value("ATTRIBUTE"): ATTRIBUTE -} - -# reverse lookup of above -# note: assuming one-to-one correspondence -name_to_grpc_base_type = dict(zip(grpc_base_type_to_name.values(), grpc_base_type_to_name.keys())) - - - diff --git a/grakn/service/Session/Concept/Concept.py b/grakn/service/Session/Concept/Concept.py deleted file mode 100644 index f8858bec..00000000 --- a/grakn/service/Session/Concept/Concept.py +++ /dev/null @@ -1,196 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - - -from grakn.service.Session.Concept import BaseTypeMapping - - -def base_type_of(concept): - return object_to_name[type(concept)] - - -class Concept(object): - - def __init__(self, grpc_concept): - self.id = grpc_concept.id - self.base_type = base_type_of(self) - - def as_remote(self, tx): - from grakn.service.Session.Concept import ConceptFactory - return ConceptFactory.create_remote_concept_base(tx._tx_service, self.id, BaseTypeMapping.name_to_grpc_base_type[base_type_of(self)]) - - def is_schema_concept(self): - """ Check if this concept is a schema concept """ - return isinstance(self, SchemaConcept) - - is_schema_concept.__annotations__ = {'return': bool} - - def is_type(self): - """ Check if this concept is a Type concept """ - return isinstance(self, Type) - - is_type.__annotations__ = {'return': bool} - - def is_thing(self): - """ Check if this concept is a Thing concept """ - return isinstance(self, Thing) - - is_thing.__annotations__ = {'return': bool} - - def is_attribute_type(self): - """ Check if this concept is an AttributeType concept """ - return isinstance(self, AttributeType) - - is_attribute_type.__annotations__ = {'return': bool} - - def is_entity_type(self): - """ Check if this concept is an EntityType concept """ - return isinstance(self, EntityType) - - is_entity_type.__annotations__ = {'return': bool} - - def is_relation_type(self): - """ Check if this concept is a RelationType concept """ - return isinstance(self, RelationType) - - is_relation_type.__annotations__ = {'return': bool} - - def is_role(self): - """ Check if this concept is a Role """ - return isinstance(self, Role) - - is_role.__annotations__ = {'return': bool} - - def is_rule(self): - """ Check if this concept is a Rule concept """ - return isinstance(self, Rule) - - is_rule.__annotations__ = {'return': bool} - - def is_attribute(self): - """ Check if this concept is an Attribute concept """ - return isinstance(self, Attribute) - - is_attribute.__annotations__ = {'return': bool} - - def is_entity(self): - """ Check if this concept is an Entity concept """ - return isinstance(self, Entity) - - is_entity.__annotations__ = {'return': bool} - - def is_relation(self): - """ Check if this concept is a Relation concept """ - return isinstance(self, Relation) - - is_relation.__annotations__ = {'return': bool} - - -class SchemaConcept(Concept): - - def __init__(self, grpc_concept): - super(SchemaConcept, self).__init__(grpc_concept) - self._label = grpc_concept.label_res.label - - def label(self): - """ - Get the label of this schema concept. - """ - return self._label - - -class Type(SchemaConcept): - pass - - -class EntityType(Type): - pass - - -class AttributeType(Type): - - def __index__(self, grpc_concept): - super(Type, self).__init__(grpc_concept) - from grakn.service.Session.util import ResponseReader - self._value_type = ResponseReader.ResponseReader.from_grpc_value_type_res(grpc_concept.valueType_res) - - def value_type(self): - """ Get the ValueType enum (grakn.ValueType) corresponding to the type of this attribute """ - return self._value_type - - -class RelationType(Type): - pass - - -class Rule(SchemaConcept): - pass - - -class Role(SchemaConcept): - pass - - -class Thing(Concept): - - def __init__(self, grpc_concept): - super(Thing, self).__init__(grpc_concept) - self._inferred = grpc_concept.inferred_res.inferred - from grakn.service.Session.Concept import ConceptFactory - self._type = ConceptFactory.create_local_concept(grpc_concept.type_res.type) - - def is_inferred(self): - return self._inferred - - def type(self): - return self._type - - -class Entity(Thing): - pass - - -class Attribute(Thing): - - def __init__(self, grpc_concept): - super(Attribute, self).__init__(grpc_concept) - from grakn.service.Session.util import ResponseReader - self._value = ResponseReader.ResponseReader.from_grpc_value_object(grpc_concept.value_res.value) - - def value(self): - return self._value - - -class Relation(Thing): - pass - - -name_to_object = { - BaseTypeMapping.META_TYPE: Type, - BaseTypeMapping.ENTITY_TYPE: EntityType, - BaseTypeMapping.RELATION_TYPE: RelationType, - BaseTypeMapping.ATTRIBUTE_TYPE: AttributeType, - BaseTypeMapping.ROLE: Role, - BaseTypeMapping.RULE: Rule, - BaseTypeMapping.ENTITY: Entity, - BaseTypeMapping.RELATION: Relation, - BaseTypeMapping.ATTRIBUTE: Attribute -} - -object_to_name = dict(zip(name_to_object.values(), name_to_object.keys())) diff --git a/grakn/service/Session/Concept/ConceptFactory.py b/grakn/service/Session/Concept/ConceptFactory.py deleted file mode 100644 index 7717296a..00000000 --- a/grakn/service/Session/Concept/ConceptFactory.py +++ /dev/null @@ -1,63 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -from grakn.service.Session.Concept import BaseTypeMapping, Concept, RemoteConcept - -# map names to ConceptHierarchy types -name_to_remote_object = { - BaseTypeMapping.META_TYPE: RemoteConcept.RemoteType, - BaseTypeMapping.ENTITY_TYPE: RemoteConcept.RemoteEntityType, - BaseTypeMapping.RELATION_TYPE: RemoteConcept.RemoteRelationType, - BaseTypeMapping.ATTRIBUTE_TYPE: RemoteConcept.RemoteAttributeType, - BaseTypeMapping.ROLE: RemoteConcept.RemoteRole, - BaseTypeMapping.RULE: RemoteConcept.RemoteRule, - BaseTypeMapping.ENTITY: RemoteConcept.RemoteEntity, - BaseTypeMapping.RELATION: RemoteConcept.RemoteRelation, - BaseTypeMapping.ATTRIBUTE: RemoteConcept.RemoteAttribute -} - - -def create_remote_concept(tx_service, grpc_concept): - - concept_id = grpc_concept.id - base_type = grpc_concept.baseType - - return create_remote_concept_base(tx_service, concept_id, base_type) - - -def create_remote_concept_base(tx_service, concept_id, base_type): - """ Instantate a local Python object for a Concept corresponding to the .baseType of the GRPC concept object """ - - concept_name = BaseTypeMapping.grpc_base_type_to_name[base_type] - concept_class = name_to_remote_object[concept_name] - - return concept_class(concept_id, concept_name, tx_service) - - -def create_local_concept(grpc_concept): - - base_type = grpc_concept.baseType - - try: - concept_name = BaseTypeMapping.grpc_base_type_to_name[base_type] - concept_class = Concept.name_to_object[concept_name] - except KeyError as ke: - raise ke - - return concept_class(grpc_concept) diff --git a/grakn/service/Session/Concept/RemoteConcept.py b/grakn/service/Session/Concept/RemoteConcept.py deleted file mode 100644 index 8ef32cd2..00000000 --- a/grakn/service/Session/Concept/RemoteConcept.py +++ /dev/null @@ -1,562 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -from grakn.service.Session.util import enums -from grakn.service.Session.util.RequestBuilder import RequestBuilder -from grakn.exception.GraknError import GraknError -from six.moves import map - - -class RemoteConcept(object): - - def __init__(self, concept_id, base_type, tx_service): - self.id = concept_id - self.base_type = base_type - self._tx_service = tx_service - - def as_remote(self, tx_service): - return type(self)(self.id, self.base_type, tx_service) - - def delete(self): - del_request = RequestBuilder.ConceptMethod.delete() - method_response = self._tx_service.run_concept_method(self.id, del_request) - return - - def is_deleted(self): - retrieved = self._tx_service.get_concept(self.id) - return retrieved is None - - def is_schema_concept(self): - """ Check if this concept is a schema concept """ - return isinstance(self, RemoteSchemaConcept) - - is_schema_concept.__annotations__ = {'return': bool} - - def is_type(self): - """ Check if this concept is a Type concept """ - return isinstance(self, RemoteType) - - is_type.__annotations__ = {'return': bool} - - def is_thing(self): - """ Check if this concept is a Thing concept """ - return isinstance(self, RemoteThing) - - is_thing.__annotations__ = {'return': bool} - - def is_attribute_type(self): - """ Check if this concept is an AttributeType concept """ - return isinstance(self, RemoteAttributeType) - - is_attribute_type.__annotations__ = {'return': bool} - - def is_entity_type(self): - """ Check if this concept is an EntityType concept """ - return isinstance(self, RemoteEntityType) - - is_entity_type.__annotations__ = {'return': bool} - - def is_relation_type(self): - """ Check if this concept is a RelationType concept """ - return isinstance(self, RemoteRelationType) - - is_relation_type.__annotations__ = {'return': bool} - - def is_role(self): - """ Check if this concept is a Role """ - return isinstance(self, RemoteRole) - - is_role.__annotations__ = {'return': bool} - - def is_rule(self): - """ Check if this concept is a Rule concept """ - return isinstance(self, RemoteRule) - - is_rule.__annotations__ = {'return': bool} - - def is_attribute(self): - """ Check if this concept is an Attribute concept """ - return isinstance(self, RemoteAttribute) - - is_attribute.__annotations__ = {'return': bool} - - def is_entity(self): - """ Check if this concept is an Entity concept """ - return isinstance(self, RemoteEntity) - - is_entity.__annotations__ = {'return': bool} - - def is_relation(self): - """ Check if this concept is a Relation concept """ - return isinstance(self, RemoteRelation) - - is_relation.__annotations__ = {'return': bool} - - -class RemoteSchemaConcept(RemoteConcept): - - def label(self, value=None): - """ - Get or set label of this schema concept. - If used as setter returns self - """ - if value is None: - get_label_req = RequestBuilder.ConceptMethod.SchemaConcept.get_label() - method_response = self._tx_service.run_concept_method(self.id, get_label_req) - return method_response.schemaConcept_getLabel_res.label - else: - set_label_req = RequestBuilder.ConceptMethod.SchemaConcept.set_label(value) - method_response = self._tx_service.run_concept_method(self.id, set_label_req) - return self - - def sup(self, super_concept=None): - """ - Get or set super schema concept. - If used as a setter returns self - """ - if super_concept is None: - # get direct super schema concept - get_sup_req = RequestBuilder.ConceptMethod.SchemaConcept.get_sup() - method_response = self._tx_service.run_concept_method(self.id, get_sup_req) - get_sup_response = method_response.schemaConcept_getSup_res - # check if received a Null or Concept - whichone = get_sup_response.WhichOneof('res') - if whichone == 'schemaConcept': - grpc_schema_concept = get_sup_response.schemaConcept - from grakn.service.Session.Concept import ConceptFactory - concept = ConceptFactory.create_remote_concept(self._tx_service, grpc_schema_concept) - return concept - elif whichone == 'null': - return None - else: - raise GraknError("Unknown response concent for getting super schema concept: {0}".format(whichone)) - else: - # set direct super SchemaConcept of this SchemaConcept - set_sup_req = RequestBuilder.ConceptMethod.SchemaConcept.set_sup(super_concept) - method_response = self._tx_service.run_concept_method(self.id, set_sup_req) - return self - - def subs(self): - """ Retrieve the sub schema concepts of this schema concept, as an iterator """ - subs_req = RequestBuilder.ConceptMethod.SchemaConcept.subs() - from grakn.service.Session.Concept import ConceptFactory - return map(lambda iter_res: - ConceptFactory.create_remote_concept(self._tx_service, - iter_res.schemaConcept_subs_iter_res.schemaConcept), - self._tx_service.run_concept_iter_method(self.id, subs_req)) - - def sups(self): - """ Retrieve the all supertypes (direct and higher level) of this schema concept as an iterator """ - sups_req = RequestBuilder.ConceptMethod.SchemaConcept.sups() - from grakn.service.Session.Concept import ConceptFactory - return map(lambda iter_res: - ConceptFactory.create_remote_concept(self._tx_service, - iter_res.schemaConcept_sups_iter_res.schemaConcept), - self._tx_service.run_concept_iter_method(self.id, sups_req)) - - -class RemoteType(RemoteSchemaConcept): - - def is_abstract(self, value=None): - """ - Get/Set whether this schema Type object is abstract. - When used as a setter returns `self` - """ - if value is None: - # return True/False if the type is set to abstract - is_abstract_req = RequestBuilder.ConceptMethod.Type.is_abstract() - method_response = self._tx_service.run_concept_method(self.id, is_abstract_req) - return method_response.type_isAbstract_res.abstract - else: - set_abstract_req = RequestBuilder.ConceptMethod.Type.set_abstract(value) - method_response = self._tx_service.run_concept_method(self.id, set_abstract_req) - return self - - is_abstract.__annotations__ = {'value': bool, 'return': bool} - - def attributes(self): - """ Retrieve all attributes attached to this Type as an iterator """ - attributes_req = RequestBuilder.ConceptMethod.Type.attributes() - from grakn.service.Session.Concept import ConceptFactory - return map(lambda iter_res: - ConceptFactory.create_remote_concept(self._tx_service, - iter_res.type_attributes_iter_res.attributeType), - self._tx_service.run_concept_iter_method(self.id, attributes_req)) - - def instances(self): - """ Retrieve all instances of this Type as an iterator """ - instances_req = RequestBuilder.ConceptMethod.Type.instances() - from grakn.service.Session.Concept import ConceptFactory - return map(lambda iter_res: - ConceptFactory.create_remote_concept(self._tx_service, - iter_res.type_instances_iter_res.thing), - self._tx_service.run_concept_iter_method(self.id, instances_req)) - - def playing(self): - """ Retrieve iterator of roles played by this type """ - playing_req = RequestBuilder.ConceptMethod.Type.playing() - from grakn.service.Session.Concept import ConceptFactory - return map(lambda iter_res: - ConceptFactory.create_remote_concept(self._tx_service, - iter_res.type_playing_iter_res.role), - self._tx_service.run_concept_iter_method(self.id, playing_req)) - - def plays(self, role_concept): - """ Set a role that is played by this Type """ - plays_req = RequestBuilder.ConceptMethod.Type.plays(role_concept) - method_response = self._tx_service.run_concept_method(self.id, plays_req) - return self - - def unplay(self, role_concept): - """ Remove a role that is played by this Type """ - unplay_req = RequestBuilder.ConceptMethod.Type.unplay(role_concept) - method_response = self._tx_service.run_concept_method(self.id, unplay_req) - return - - def has(self, attribute_concept_type): - """ Attach an attributeType concept to the type """ - has_req = RequestBuilder.ConceptMethod.Type.has(attribute_concept_type) - method_response = self._tx_service.run_concept_method(self.id, has_req) - return self - - def unhas(self, attribute_concept_type): - """ Remove an attribute type concept from this type """ - unhas_req = RequestBuilder.ConceptMethod.Type.unhas(attribute_concept_type) - method_response = self._tx_service.run_concept_method(self.id, unhas_req) - return self - - def keys(self): - """ Retrieve an iterator of attribute types that this Type uses as keys """ - keys_req = RequestBuilder.ConceptMethod.Type.keys() - from grakn.service.Session.Concept import ConceptFactory - return map(lambda iter_res: - ConceptFactory.create_remote_concept(self._tx_service, - iter_res.type_keys_iter_res.attributeType), - self._tx_service.run_concept_iter_method(self.id, keys_req)) - - def key(self, attribute_concept_type): - """ Add an attribute type to be a key for this Type """ - key_req = RequestBuilder.ConceptMethod.Type.key(attribute_concept_type) - method_response = self._tx_service.run_concept_method(self.id, key_req) - return self - - def unkey(self, attribute_concept_type): - """ Remove an attribute type from this Type from being a key """ - unkey_req = RequestBuilder.ConceptMethod.Type.unkey(attribute_concept_type) - method_response = self._tx_service.run_concept_method(self.id, unkey_req) - return self - - -class RemoteEntityType(RemoteType): - - def create(self): - """ Instantiate an entity of the given type and return it """ - create_req = RequestBuilder.ConceptMethod.EntityType.create() - method_response = self._tx_service.run_concept_method(self.id, create_req) - grpc_entity_concept = method_response.entityType_create_res.entity - from grakn.service.Session.Concept import ConceptFactory - return ConceptFactory.create_remote_concept(self._tx_service, grpc_entity_concept) - - -class RemoteAttributeType(RemoteType): - - def create(self, value): - """ Create an instance with this AttributeType """ - self_value_type = self.value_type() - create_inst_req = RequestBuilder.ConceptMethod.AttributeType.create(value, self_value_type) - method_response = self._tx_service.run_concept_method(self.id, create_inst_req) - grpc_attribute_concept = method_response.attributeType_create_res.attribute - from grakn.service.Session.Concept import ConceptFactory - return ConceptFactory.create_remote_concept(self._tx_service, grpc_attribute_concept) - - def attribute(self, value): - """ Retrieve an attribute instance by value if it exists """ - self_value_type = self.value_type() - get_attribute_req = RequestBuilder.ConceptMethod.AttributeType.attribute(value, self_value_type) - method_response = self._tx_service.run_concept_method(self.id, get_attribute_req) - response = method_response.attributeType_attribute_res - whichone = response.WhichOneof('res') - if whichone == 'attribute': - from grakn.service.Session.Concept import ConceptFactory - return ConceptFactory.create_remote_concept(self._tx_service, response.attribute) - elif whichone == 'null': - return None - else: - raise GraknError("Unknown `res` key in AttributeType `attribute` response: {0}".format(whichone)) - - def value_type(self): - """ Get the ValueType enum (grakn.ValueType) corresponding to the type of this attribute """ - get_value_type_req = RequestBuilder.ConceptMethod.AttributeType.value_type() - method_response = self._tx_service.run_concept_method(self.id, get_value_type_req) - response = method_response.attributeType_valueType_res - whichone = response.WhichOneof('res') - if whichone == 'valueType': - # iterate over enum ValueType enum to find matching data type - for e in enums.ValueType: - if e.value == response.valueType: - return e - else: - # loop exited normally - raise GraknError("Reported valuetype NOT in enum: {0}".format(response.valueType)) - elif whichone == 'null': - return None - else: - raise GraknError("Unknown valuetype response for AttributeType: {0}".format(whichone)) - - def regex(self, pattern=None): - """ Get or set regex """ - if pattern is None: - get_regex_req = RequestBuilder.ConceptMethod.AttributeType.get_regex() - method_response = self._tx_service.run_concept_method(self.id, get_regex_req) - return method_response.attributeType_getRegex_res.regex - else: - set_regex_req = RequestBuilder.ConceptMethod.AttributeType.set_regex(pattern) - method_response = self._tx_service.run_concept_method(self.id, set_regex_req) - return self - - regex.__annotations__ = {'pattern': str} - - -class RemoteRelationType(RemoteType): - def create(self): - """ Create an instance of a relation with this type """ - create_rel_inst_req = RequestBuilder.ConceptMethod.RelationType.create() - method_response = self._tx_service.run_concept_method(self.id, create_rel_inst_req) - grpc_relation_concept = method_response.relationType_create_res.relation - from grakn.service.Session.Concept import ConceptFactory - return ConceptFactory.create_remote_concept(self._tx_service, grpc_relation_concept) - - def roles(self): - """ Retrieve roles in this relation schema type """ - get_roles = RequestBuilder.ConceptMethod.RelationType.roles() - from grakn.service.Session.Concept import ConceptFactory - return map(lambda iter_res: - ConceptFactory.create_remote_concept(self._tx_service, - iter_res.relationType_roles_iter_res.role), - self._tx_service.run_concept_iter_method(self.id, get_roles)) - - - def relates(self, role): - """ Set a role in this relation schema type """ - relates_req = RequestBuilder.ConceptMethod.RelationType.relates(role) - method_response = self._tx_service.run_concept_method(self.id, relates_req) - return self - - def unrelate(self, role): - """ Remove a role in this relation schema type """ - unrelate_req = RequestBuilder.ConceptMethod.RelationType.unrelate(role) - method_response = self._tx_service.run_concept_method(self.id, unrelate_req) - return self - - -class RemoteRule(RemoteSchemaConcept): - - def get_when(self): - """ Retrieve the `when` clause for this rule """ - when_req = RequestBuilder.ConceptMethod.Rule.when() - method_response = self._tx_service.run_concept_method(self.id, when_req) - response = method_response.rule_when_res - whichone = response.WhichOneof('res') - if whichone == 'pattern': - return response.pattern - elif whichone == 'null': - return None - else: - raise GraknError("Unknown field in get_when of `rule`: {0}".format(whichone)) - - def get_then(self): - """ Retrieve the `then` clause for this rule """ - then_req = RequestBuilder.ConceptMethod.Rule.then() - method_response = self._tx_service.run_concept_method(self.id, then_req) - response = method_response.rule_then_res - whichone = response.WhichOneof('res') - if whichone == 'pattern': - return response.pattern - elif whichone == 'null': - return None - else: - raise GraknError("Unknown field in get_then or `rule`: {0}".format(whichone)) - - -class RemoteRole(RemoteSchemaConcept): - - def relations(self): - """ Retrieve relations that this role participates in, as an iterator """ - relations_req = RequestBuilder.ConceptMethod.Role.relations() - from grakn.service.Session.Concept import ConceptFactory - return map(lambda iter_res: - ConceptFactory.create_remote_concept(self._tx_service, - iter_res.role_relations_iter_res.relationType), - self._tx_service.run_concept_iter_method(self.id, relations_req)) - - def players(self): - """ Retrieve an iterator of entities that play this role """ - players_req = RequestBuilder.ConceptMethod.Role.players() - from grakn.service.Session.Concept import ConceptFactory - return map(lambda iter_res: - ConceptFactory.create_remote_concept(self._tx_service, - iter_res.role_players_iter_res.type), - self._tx_service.run_concept_iter_method(self.id, players_req)) - - -class RemoteThing(RemoteConcept): - - def is_inferred(self): - """ Is this instance inferred """ - is_inferred_req = RequestBuilder.ConceptMethod.Thing.is_inferred() - method_response = self._tx_service.run_concept_method(self.id, is_inferred_req) - return method_response.thing_isInferred_res.inferred - - is_inferred.__annotations__ = {'return': bool} - - def type(self): - """ Get the type (schema concept) of this Thing """ - type_req = RequestBuilder.ConceptMethod.Thing.type() - method_response = self._tx_service.run_concept_method(self.id, type_req) - from grakn.service.Session.Concept import ConceptFactory - return ConceptFactory.create_remote_concept(self._tx_service, method_response.thing_type_res.type) - - def relations(self, *roles): - """ Get iterator this Thing's relations, filtered to the optionally provided roles """ - relations_req = RequestBuilder.ConceptMethod.Thing.relations(roles) - from grakn.service.Session.Concept import ConceptFactory - return map(lambda iter_res: - ConceptFactory.create_remote_concept(self._tx_service, - iter_res.thing_relations_iter_res.relation), - self._tx_service.run_concept_iter_method(self.id, relations_req)) - - def attributes(self, *attribute_types): - """ Retrieve iterator of this Thing's attributes, filtered by optionally provided attribute types """ - attrs_req = RequestBuilder.ConceptMethod.Thing.attributes(attribute_types) - from grakn.service.Session.Concept import ConceptFactory - return map(lambda iter_res: - ConceptFactory.create_remote_concept(self._tx_service, - iter_res.thing_attributes_iter_res.attribute), - self._tx_service.run_concept_iter_method(self.id, attrs_req)) - - def roles(self): - """ Retrieve iterator of roles this Thing plays """ - roles_req = RequestBuilder.ConceptMethod.Thing.roles() - from grakn.service.Session.Concept import ConceptFactory - return map(lambda iter_res: - ConceptFactory.create_remote_concept(self._tx_service, - iter_res.thing_roles_iter_res.role), - self._tx_service.run_concept_iter_method(self.id, roles_req)) - - def keys(self, *attribute_types): - """ Retrieve iterator of keys (i.e. actual attributes) of this Thing, filtered by the optionally provided attribute types """ - keys_req = RequestBuilder.ConceptMethod.Thing.keys(attribute_types) - from grakn.service.Session.Concept import ConceptFactory - return map(lambda iter_res: - ConceptFactory.create_remote_concept(self._tx_service, - iter_res.thing_keys_iter_res.attribute), - self._tx_service.run_concept_iter_method(self.id, keys_req)) - - def has(self, attribute): - """ Attach an attribute instance to this Thing """ - has_req = RequestBuilder.ConceptMethod.Thing.has(attribute) - method_response = self._tx_service.run_concept_method(self.id, has_req) - return - - def unhas(self, attribute): - """ Remove an attribute instance from this Thing """ - unhas_req = RequestBuilder.ConceptMethod.Thing.unhas(attribute) - method_response = self._tx_service.run_concept_method(self.id, unhas_req) - return - - -class RemoteEntity(RemoteThing): - pass - - -class RemoteAttribute(RemoteThing): - - def value(self): - """ Retrieve the value contained in this Attribute instance """ - value_req = RequestBuilder.ConceptMethod.Attribute.value() - method_response = self._tx_service.run_concept_method(self.id, value_req) - grpc_value_object = method_response.attribute_value_res.value - from grakn.service.Session.util import ResponseReader - return ResponseReader.ResponseReader.from_grpc_value_object(grpc_value_object) - - def owners(self): - """ Retrieve entities that have this attribute value """ - owners_req = RequestBuilder.ConceptMethod.Attribute.owners() - from grakn.service.Session.Concept import ConceptFactory - return map(lambda iter_res: - ConceptFactory.create_remote_concept(self._tx_service, - iter_res.attribute_owners_iter_res.thing), - self._tx_service.run_concept_iter_method(self.id, owners_req)) - - -class RemoteRelation(RemoteThing): - - def role_players_map(self): - """ Retrieve dictionary {role : set(players)} for this relation """ - role_players_map_req = RequestBuilder.ConceptMethod.Relation.role_players_map() - - tx_service = self._tx_service - # create the iterator to obtain all the pairs of (role, player) - def to_pair(iter_res): - response = iter_res.relation_rolePlayersMap_iter_res - from grakn.service.Session.Concept import ConceptFactory - role = ConceptFactory.create_remote_concept(tx_service, response.role) - from grakn.service.Session.Concept import ConceptFactory - player = ConceptFactory.create_remote_concept(tx_service, response.player) - return (role, player) - - # collect all pairs of (role, player) from the iterator (executes over network to Grakn server) - pairs = list(map(to_pair, self._tx_service.run_concept_iter_method(self.id, role_players_map_req))) - - # aggregate into a map from role to set(player) - # note: need to use role ID as the map key ultimately - mapping = {} - id_mapping = {} - for (role, player) in pairs: - role_id = role.id - if role_id in id_mapping: - role_key = id_mapping[role_id] - else: - id_mapping[role_id] = role - role_key = role - mapping[role_key] = [] - mapping[role_key].append(player) - - return mapping - - def role_players(self, *roles): - """ Retrieve role players filtered by roles """ - role_players_req = RequestBuilder.ConceptMethod.Relation.role_players(roles) - from grakn.service.Session.Concept import ConceptFactory - return map(lambda iter_res: - ConceptFactory.create_remote_concept(self._tx_service, - iter_res.relation_rolePlayers_iter_res.thing), - self._tx_service.run_concept_iter_method(self.id, role_players_req)) - - def assign(self, role, thing): - """ Assign an entity to a role on this relation instance """ - assign_req = RequestBuilder.ConceptMethod.Relation.assign(role, thing) - method_response = self._tx_service.run_concept_method(self.id, assign_req) - return self - - def unassign(self, role, thing): - """ Un-assign an entity from a role on this relation instance """ - unassign_req = RequestBuilder.ConceptMethod.Relation.unassign(role, thing) - method_response = self._tx_service.run_concept_method(self.id, unassign_req) - return self diff --git a/grakn/service/Session/TransactionService.py b/grakn/service/Session/TransactionService.py deleted file mode 100644 index 3c800e6d..00000000 --- a/grakn/service/Session/TransactionService.py +++ /dev/null @@ -1,181 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -import six -from six.moves import map - -from grakn.service.Session.util.RequestBuilder import RequestBuilder -import grakn.service.Session.util.ResponseReader as ResponseReader # for circular import issue -from grakn.service.Session.util import enums -from grakn.service.Session.util.Communicator import Communicator -from grakn.exception.GraknError import GraknError - - -class TransactionService(object): - - def __init__(self, session_id, tx_type, transaction_endpoint): - self.session_id = session_id - self.tx_type = tx_type.value - - self._communicator = Communicator(transaction_endpoint) - - # open the transaction with an 'open' message - open_req = RequestBuilder.open_tx(session_id, tx_type) - self._communicator.single_request(open_req) - __init__.__annotations__ = {'tx_type': enums.TxType} - - # --- Passthrough targets --- - # targets of top level Transaction class - - def query(self, query, infer, explain, batch_size): - return Iterator(self._communicator, - RequestBuilder.start_iterating_query(query, infer, explain, batch_size), - ResponseReader.ResponseReader.get_query_results(self)) - query.__annotations__ = {'query': str} - - def commit(self): - request = RequestBuilder.commit() - self._communicator.single_request(request) - - def close(self): - self._communicator.close() - - def is_closed(self): - return self._communicator._closed - - def get_concept(self, concept_id): - request = RequestBuilder.get_concept(concept_id) - response = self._communicator.single_request(request) - return ResponseReader.ResponseReader.get_concept(self, response.getConcept_res) - get_concept.__annotations__ = {'concept_id': str} - - def get_schema_concept(self, label): - request = RequestBuilder.get_schema_concept(label) - response = self._communicator.single_request(request) - return ResponseReader.ResponseReader.get_schema_concept(self, response.getSchemaConcept_res) - get_schema_concept.__annotations__ = {'label': str} - - def get_attributes_by_value(self, attribute_value, value_type): - request = RequestBuilder.start_iterating_get_attributes_by_value(attribute_value, value_type) - return Iterator(self._communicator, request, ResponseReader.ResponseReader.get_attributes_by_value(self)) - get_attributes_by_value.__annotations__ = {'data_type': enums.ValueType} - - def put_entity_type(self, label): - request = RequestBuilder.put_entity_type(label) - response = self._communicator.single_request(request) - return ResponseReader.ResponseReader.put_entity_type(self, response.putEntityType_res) - put_entity_type.__annotations__ = {'label': str} - - def put_relation_type(self, label): - request = RequestBuilder.put_relation_type(label) - response = self._communicator.single_request(request) - return ResponseReader.ResponseReader.put_relation_type(self, response.putRelationType_res) - put_relation_type.__annotations__ = {'label': str} - - def put_attribute_type(self, label, value_type): - request = RequestBuilder.put_attribute_type(label, value_type) - response = self._communicator.single_request(request) - return ResponseReader.ResponseReader.put_attribute_type(self, response.putAttributeType_res) - put_attribute_type.__annotations__ = {'label': str, 'value_type': enums.ValueType} - - def put_role(self, label): - request = RequestBuilder.put_role(label) - response = self._communicator.single_request(request) - return ResponseReader.ResponseReader.put_role(self, response.putRole_res) - put_role.__annotations__ = {'label': str} - - def put_rule(self, label, when, then): - request = RequestBuilder.put_rule(label, when, then) - response = self._communicator.single_request(request) - return ResponseReader.ResponseReader.put_rule(self, response.putRule_res) - put_rule.__annotations__ = {'label': str, 'when': str, 'then': str} - - # --- Transaction Messages --- - - def run_concept_method(self, concept_id, grpc_concept_method_req): - # wrap method_req into a transaction message - tx_request = RequestBuilder.concept_method_req_to_tx_req(concept_id, grpc_concept_method_req) - response = self._communicator.single_request(tx_request) - return response.conceptMethod_res.response - - def run_concept_iter_method(self, concept_id, grpc_concept_iter_method_req): - return Iterator(self._communicator, - RequestBuilder.start_iterating_concept_method(concept_id, grpc_concept_iter_method_req), - lambda res: res.conceptMethod_iter_res.response) - - def explanation(self, explainable): - """ Retrieve the explanation of a Concept Map from the server """ - tx_request = RequestBuilder.explanation(explainable) - response = self._communicator.single_request(tx_request) - return ResponseReader.ResponseReader.create_explanation(self, response.explanation_res) - - -end_of_batch_results = {'done', 'iteratorId'} - - -def end_of_batch(res): - res_type = res.iter_res.WhichOneof('res') - return res_type == 'done' or res_type == 'iteratorId' - - -class Iterator(six.Iterator): - def __init__(self, communicator, iter_req, resp_converter): - self._communicator = communicator - self._iter_req = iter_req - self._response_iterator = self._communicator.iteration_request( - RequestBuilder.iter_req_to_tx_req(self._iter_req), - end_of_batch) - self._done = False - self._resp_converter = resp_converter - - def __iter__(self): - return self - - def _request_next_batch(self, iter_id): - self._response_iterator = self._communicator.iteration_request( - RequestBuilder.iter_req_to_tx_req( - RequestBuilder.continue_iterating(iter_id, self._iter_req.options)), - end_of_batch) - - def __next__(self): - if self._done: - raise GraknError('Iterator was already iterated.') - - try: - response = next(self._response_iterator) - except StopIteration: - raise GraknError('Internal client/protocol error,' - ' did not receive an expected "done" or "iteratorId" message.' - '\n\n Please ensure client version is supported by server version.') - - iter_res = response.iter_res - res_type = iter_res.WhichOneof('res') - if res_type == 'done': - self._done = True - raise StopIteration - elif res_type == 'iteratorId': - self._request_next_batch(iter_res.iteratorId) - return next(self) - else: - return self._resp_converter(iter_res) - - def get(self): - if not self._done: - self._response_iterator.get() - return self diff --git a/grakn/service/Session/util/Communicator.py b/grakn/service/Session/util/Communicator.py deleted file mode 100644 index 0893eaed..00000000 --- a/grakn/service/Session/util/Communicator.py +++ /dev/null @@ -1,219 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -# Communicator Architecture -# ========================= -# -# This communicator allows us to track the sending of multiple requests and match their responses, which may be streams -# of multiple results, without blocking the initial request call and without any complex threading. This allows the -# usage of a single stream from a single thread to have predictable results, as there will never be race conditions -# between requests. -# -# The result is that requests are: -# * Async: like promises. -# * Serial: always made in the order they are requested by the caller thread. -# -# When a request is made, a Resolver is pushed onto the `resolver_queue`. The Resolver is used to *pull* the correct -# responses from the GRPC stream, which must arrive in a corresponding order but could be pulled by the client in any -# order. There is no "push" from GRPC, the GRPC stream is pulled until the correct responses are found. -# -# No matter which resolver is being resolved, the resolvers *must* be iterated in the order of the `resolver_queue`, -# since that wil be the order of the responses. If another resolver is ahead of the one we want to retrieve responses -# for, any responses we find are pushed to its buffer, so that when the user later retrieves responses through that -# resolver, it will not miss any. **This push only happens when trying to retrieve results out of the order you request -# them**, it does not happen in a separate thread! -# -# As a final step, closing the transaction must ensure that at least one valid response is received for each resolver, -# to catch and raise any errors before returning control to the user code. - - -import six -from collections import deque -from six.moves import queue -from grakn.exception.GraknError import GraknError - - -class SingleResolver: - def __init__(self, communicator, request): - self._request = request - self._communicator = communicator - self._buffered_response = None - communicator._send_with_resolver(self, request) - - def _is_last_response(self, response): - return True - - def _buffer_response(self, response): - self._buffered_response = response - - def get(self): - response = self._buffered_response - if response: - return response - else: - response = self._communicator._block_for_next(self) - self._buffered_response = response - return response - - def _end(self): - pass - - def _on_close(self): - # Ensure any error is correctly raised by waiting for the response even if we are closing - self.get() - - -class IterationResolver(six.Iterator): - def __init__(self, communicator, request, is_last_response): - self._request = request - self._is_last_response = is_last_response - self._communicator = communicator - self._ended = False - self._started = False - self._response_buffer = deque() - communicator._send_with_resolver(self, request) - - def __iter__(self): - return self - - def __next__(self): - # Regardless of the outcome, we must have a result or error before this method returns - self._started = True - try: - # We should first return any results that another resolver has buffered for us - return self._response_buffer.popleft() - except IndexError: - if self._ended: - raise StopIteration() - # Block on the GRPC stream and returns the next result, not requiring the buffer - # This may buffer results for other resolvers - return self._communicator._block_for_next(self) - - def _buffer_response(self, response): - # Only called when another resolver is pushing a result into our buffer, this may happen before first __next__ - self._started = True - self._response_buffer.append(response) - - def _end(self): - self._ended = True - - def _on_close(self): - try: - while not self._ended: - response = self._communicator._block_for_next(self) - self._buffer_response(response) - except StopIteration: - pass - - def get(self): - if self._started: - return self - try: - self._response_buffer.append(next(self)) - except StopIteration: - pass - - -class Communicator(six.Iterator): - def __init__(self, grpc_stream_constructor): - self._request_queue = queue.Queue() - self._resolver_queue = deque() - self._response_iterator = grpc_stream_constructor(self) - self._closed = False - self._error = None - - def __iter__(self): - return self - - # Used by the GRPC stream to iterate requests as they arrive - def __next__(self): - request = self._request_queue.get(block=True) - if request is None: - raise StopIteration() - return request - - def error_if_closed(self): - if self._closed: - raise GraknError("This connection is closed") - - # Put a request for GRPC to consume - def _send_with_resolver(self, resolver, request): - self._resolver_queue.append(resolver) - self._request_queue.put(request) - - def _block_for_next(self, resolver): - self.error_if_closed() - while True: - current = None - try: - current = self._resolver_queue[0] - response = next(self._response_iterator) - - if current._is_last_response(response): - self._resolver_queue.popleft() - current._end() - - if current is resolver: - return response - else: - current._buffer_response(response) - - except Exception as e: - if not current: - self._error = GraknError("Internal client/protocol error, request/response pair not matched: {0}\n\n " - "Ensure client version is compatible with server version.".format(e)) - else: - self._error = GraknError("Server/network error: {0}\n\n generated from request: {1}".format(e, current._request)) - self.close() - raise self._error - - def iteration_request(self, request, is_last_response): - self.error_if_closed() - return IterationResolver(self, request, is_last_response) - - def single_request(self, request): - self.error_if_closed() - return SingleResolver(self, request).get() - - def close(self): - if not self._closed: - raise_error = False - if not self._error: - # Exhaust all resolvers, ensuring that transaction is closed without error - try: - for resolver in list(self._resolver_queue): - resolver._on_close() - except GraknError as e: - self._error = e - raise_error = True - self._closed = True - - with self._request_queue.mutex: # probably don't even need the mutex - self._request_queue.queue.clear() - self._request_queue.put(None) - - # force exhaust the iterator so `onCompleted()` is called on the server - if not self._error: - try: - next(self._response_iterator) - except StopIteration: - pass - - if raise_error: - raise self._error diff --git a/grakn/service/Session/util/RequestBuilder.py b/grakn/service/Session/util/RequestBuilder.py deleted file mode 100644 index 16899357..00000000 --- a/grakn/service/Session/util/RequestBuilder.py +++ /dev/null @@ -1,695 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# -from datetime import datetime - -import grakn_protocol.session.Session_pb2 as transaction_messages -import grakn_protocol.session.Concept_pb2 as concept_messages -import grakn_protocol.session.Answer_pb2 as answer_messages -from grakn.service.Session.util import enums -from grakn.service.Session.Concept import BaseTypeMapping -from grakn.exception import GraknError - -class QueryOptions(object): - SERVER_DEFAULT = None - BATCH_ALL = "all" - -class RequestBuilder(object): - """ Static methods for generating GRPC requests """ - - @staticmethod - def _base_iterate_with_options(batch_size): - iter_options = transaction_messages.Transaction.Iter.Req.Options() - if batch_size == QueryOptions.BATCH_ALL: - iter_options.all = True - elif type(batch_size) == int and batch_size > 0: - iter_options.number = batch_size - elif batch_size != QueryOptions.SERVER_DEFAULT: - raise GraknError("batch_size parameter must either be an integer, SERVER_DEFAULT, or BATCH_ALL") - - transaction_iter_req = transaction_messages.Transaction.Iter.Req() - transaction_iter_req.options.CopyFrom(iter_options) - return transaction_iter_req - - @staticmethod - def iter_req_to_tx_req(grpc_iter_req): - transaction_req = transaction_messages.Transaction.Req() - transaction_req.iter_req.CopyFrom(grpc_iter_req) - return transaction_req - - @staticmethod - def _query_options(infer, explain): - options_message = transaction_messages.Transaction.Query.Options() - if infer != QueryOptions.SERVER_DEFAULT: - if type(infer) == bool: - options_message.inferFlag = infer - else: - raise GraknError("query 'infer' flag must be SERVER_DEFAULT or a boolean") - if explain != QueryOptions.SERVER_DEFAULT: - if type(explain) == bool: - options_message.explainFlag = explain - else: - raise GraknError("query 'explain' flag must be SERVER_DEFAULT or a boolean") - return options_message - - @staticmethod - def start_iterating_query(query, infer, explain, batch_size): - query_message = transaction_messages.Transaction.Query.Iter.Req() - query_message.query = query - query_options = RequestBuilder._query_options(infer, explain) - query_message.options.CopyFrom(query_options) - transaction_iter_req = RequestBuilder._base_iterate_with_options(batch_size) - transaction_iter_req.query_iter_req.CopyFrom(query_message) - return transaction_iter_req - - @staticmethod - def start_iterating_concept_method(concept_id, grpc_concept_method_iter_req, batch_size=None): - transaction_concept_method_iter_req = transaction_messages.Transaction.ConceptMethod.Iter.Req() - transaction_concept_method_iter_req.id = concept_id - transaction_concept_method_iter_req.method.CopyFrom(grpc_concept_method_iter_req) - - transaction_iter_req = RequestBuilder._base_iterate_with_options(batch_size) - transaction_iter_req.conceptMethod_iter_req.CopyFrom(transaction_concept_method_iter_req) - return transaction_iter_req - - def start_iterating_get_attributes_by_value(value, valuetype, batch_size=None): - get_attrs_req = transaction_messages.Transaction.GetAttributes.Iter.Req() - grpc_value_object = RequestBuilder.ConceptMethod.as_value_object(value, valuetype) - get_attrs_req.value.CopyFrom(grpc_value_object) - - transaction_iter_req = RequestBuilder._base_iterate_with_options(batch_size) - transaction_iter_req.getAttributes_iter_req.CopyFrom(get_attrs_req) - return transaction_iter_req - start_iterating_get_attributes_by_value.__annotations__ = {'valuetype': enums.ValueType} - start_iterating_get_attributes_by_value = staticmethod(start_iterating_get_attributes_by_value) - - @staticmethod - def continue_iterating(iterator_id, batch_options): - transaction_iter_req = transaction_messages.Transaction.Iter.Req() - transaction_iter_req.options.CopyFrom(batch_options) - transaction_iter_req.iteratorId = iterator_id - return transaction_iter_req - - @staticmethod - def concept_method_req_to_tx_req(concept_id, grpc_concept_method_req): - concept_method_req = transaction_messages.Transaction.ConceptMethod.Req() - concept_method_req.id = concept_id - concept_method_req.method.CopyFrom(grpc_concept_method_req) - - transaction_req = transaction_messages.Transaction.Req() - transaction_req.conceptMethod_req.CopyFrom(concept_method_req) - return transaction_req - - # --- Top level functionality --- - @staticmethod - def open_tx(session_id, tx_type): - open_request = transaction_messages.Transaction.Open.Req() - open_request.sessionId = session_id - open_request.type = tx_type.value - - transaction_req = transaction_messages.Transaction.Req() - transaction_req.open_req.CopyFrom(open_request) - return transaction_req - - @staticmethod - def open_session(keyspace, credentials): - open_session_request = transaction_messages.Session.Open.Req() - open_session_request.Keyspace = keyspace - if credentials is not None: - open_session_request.username = credentials['username'] - open_session_request.password = credentials['password'] - return open_session_request - - @staticmethod - def close_session(session_id): - close_session_request = transaction_messages.Session.Close.Req() - close_session_request.sessionId = session_id - return close_session_request - - @staticmethod - def commit(): - commit_req = transaction_messages.Transaction.Commit.Req() - transaction_req = transaction_messages.Transaction.Req() - transaction_req.commit_req.CopyFrom(commit_req) - return transaction_req - - @staticmethod - def get_concept(concept_id): - get_concept_req = transaction_messages.Transaction.GetConcept.Req() - get_concept_req.id = concept_id - transaction_req = transaction_messages.Transaction.Req() - transaction_req.getConcept_req.CopyFrom(get_concept_req) - return transaction_req - - @staticmethod - def get_schema_concept(label): - get_schema_concept_req = transaction_messages.Transaction.GetSchemaConcept.Req() - get_schema_concept_req.label = label - transaction_req = transaction_messages.Transaction.Req() - transaction_req.getSchemaConcept_req.CopyFrom(get_schema_concept_req) - return transaction_req - - @staticmethod - def put_entity_type(label): - put_entity_type_req = transaction_messages.Transaction.PutEntityType.Req() - put_entity_type_req.label = label - transaction_req = transaction_messages.Transaction.Req() - transaction_req.putEntityType_req.CopyFrom(put_entity_type_req) - return transaction_req - - @staticmethod - def put_relation_type(label): - put_relation_type_req = transaction_messages.Transaction.PutRelationType.Req() - put_relation_type_req.label = label - transaction_req = transaction_messages.Transaction.Req() - transaction_req.putRelationType_req.CopyFrom(put_relation_type_req) - return transaction_req - - def put_attribute_type(label, value_type): - put_attribute_type_req = transaction_messages.Transaction.PutAttributeType.Req() - put_attribute_type_req.label = label - put_attribute_type_req.valueType = value_type.value # retrieve enum value - transaction_req = transaction_messages.Transaction.Req() - transaction_req.putAttributeType_req.CopyFrom(put_attribute_type_req) - return transaction_req - put_attribute_type.__annotations__ = {'value_type': enums.ValueType} - put_attribute_type = staticmethod(put_attribute_type) - - @staticmethod - def put_role(label): - put_role_req = transaction_messages.Transaction.PutRole.Req() - put_role_req.label = label - transaction_req = transaction_messages.Transaction.Req() - transaction_req.putRole_req.CopyFrom(put_role_req) - return transaction_req - - @staticmethod - def put_rule(label, when, then): - put_rule_req = transaction_messages.Transaction.PutRule.Req() - put_rule_req.label = label - put_rule_req.when = when - put_rule_req.then = then - transaction_req = transaction_messages.Transaction.Req() - transaction_req.putRule_req.CopyFrom(put_rule_req) - return transaction_req - - # --- internal requests --- - - @staticmethod - def explanation(explainable): - concept_map = {} - for variable, concept in explainable.map().items(): - grpc_concept = RequestBuilder.ConceptMethod._concept_to_grpc_concept(concept) - concept_map[variable] = grpc_concept - - grpc_concept_map = answer_messages.ConceptMap(map=concept_map) - - grpc_concept_map.hasExplanation = explainable.has_explanation() - grpc_concept_map.pattern = explainable.query_pattern() - - explanation_req = answer_messages.Explanation.Req() - explanation_req.explainable.CopyFrom(grpc_concept_map) - - transaction_req = transaction_messages.Transaction.Req() - transaction_req.explanation_req.CopyFrom(explanation_req) - - return transaction_req - - # ------ Concept Method Requests ------ - - class ConceptMethod(object): - """ Construct Concept Method requests """ - - @staticmethod - def delete(): - delete_req = concept_messages.Concept.Delete.Req() - concept_method_req = concept_messages.Method.Req() - concept_method_req.concept_delete_req.CopyFrom(delete_req) - return concept_method_req - - @staticmethod - def _concept_to_grpc_concept(concept): - """ Takes a concept from ConceptHierarcy and converts to GRPC message """ - grpc_concept = concept_messages.Concept() - grpc_concept.id = concept.id - base_type_name = concept.base_type - grpc_base_type = BaseTypeMapping.name_to_grpc_base_type[base_type_name] - grpc_concept.baseType = grpc_base_type - return grpc_concept - - def as_value_object(data, valuetype): - msg = concept_messages.ValueObject() - if valuetype == enums.ValueType.STRING: - msg.string = data - elif valuetype == enums.ValueType.BOOLEAN: - msg.boolean = data - elif valuetype == enums.ValueType.INTEGER: - msg.integer = data - elif valuetype == enums.ValueType.LONG: - msg.long = data - elif valuetype == enums.ValueType.FLOAT: - msg.float = data - elif valuetype == enums.ValueType.DOUBLE: - msg.double = data - elif valuetype == enums.ValueType.DATETIME: - # convert local datetime into long - epoch = datetime(1970, 1, 1) - diff = data - epoch - epoch_seconds_utc = int(diff.total_seconds()) - epoch_ms_long_utc = int(epoch_seconds_utc*1000) - msg.datetime = epoch_ms_long_utc - else: - # TODO specialize exception - raise Exception("Unknown attribute valuetype: {}".format(valuetype)) - return msg - as_value_object.__annotations__ = {'valuetype': enums.ValueType} - as_value_object = staticmethod(as_value_object) - - - - class SchemaConcept(object): - """ Generates SchemaConcept method messages """ - - @staticmethod - def get_label(): - get_schema_label_req = concept_messages.SchemaConcept.GetLabel.Req() - concept_method_req = concept_messages.Method.Req() - concept_method_req.schemaConcept_getLabel_req.CopyFrom(get_schema_label_req) - return concept_method_req - - @staticmethod - def set_label(label): - set_schema_label_req = concept_messages.SchemaConcept.SetLabel.Req() - set_schema_label_req.label = label - concept_method_req = concept_messages.Method.Req() - concept_method_req.schemaConcept_setLabel_req.CopyFrom(set_schema_label_req) - return concept_method_req - - - @staticmethod - def get_sup(): - get_sup_req = concept_messages.SchemaConcept.GetSup.Req() - concept_method_req = concept_messages.Method.Req() - concept_method_req.schemaConcept_getSup_req.CopyFrom(get_sup_req) - return concept_method_req - - @staticmethod - def set_sup(concept): - grpc_concept = RequestBuilder.ConceptMethod._concept_to_grpc_concept(concept) - set_sup_req = concept_messages.SchemaConcept.SetSup.Req() - set_sup_req.schemaConcept.CopyFrom(grpc_concept) - concept_method_req = concept_messages.Method.Req() - concept_method_req.schemaConcept_setSup_req.CopyFrom(set_sup_req) - return concept_method_req - - @staticmethod - def subs(): - subs_req = concept_messages.SchemaConcept.Subs.Iter.Req() - concept_method_req = concept_messages.Method.Iter.Req() - concept_method_req.schemaConcept_subs_iter_req.CopyFrom(subs_req) - return concept_method_req - - @staticmethod - def sups(): - sups_req = concept_messages.SchemaConcept.Sups.Iter.Req() - concept_method_req = concept_messages.Method.Iter.Req() - concept_method_req.schemaConcept_sups_iter_req.CopyFrom(sups_req) - return concept_method_req - - class Rule(object): - """ Generates Rule method messages """ - - @staticmethod - def when(): - when_req = concept_messages.Rule.When.Req() - concept_method_req = concept_messages.Method.Req() - concept_method_req.rule_when_req.CopyFrom(when_req) - return concept_method_req - - @staticmethod - def then(): - then_req = concept_messages.Rule.Then.Req() - concept_method_req = concept_messages.Method.Req() - concept_method_req.rule_then_req.CopyFrom(then_req) - return concept_method_req - - class Role(object): - """ Generates Role method messages """ - - @staticmethod - def relations(): - relations_req = concept_messages.Role.Relations.Iter.Req() - concept_method_req = concept_messages.Method.Iter.Req() - concept_method_req.role_relations_iter_req.CopyFrom(relations_req) - return concept_method_req - - @staticmethod - def players(): - players_req = concept_messages.Role.Players.Iter.Req() - concept_method_req = concept_messages.Method.Iter.Req() - concept_method_req.role_players_iter_req.CopyFrom(players_req) - return concept_method_req - - class Type(object): - """ Generates Type method messages """ - - @staticmethod - def is_abstract(): - is_abstract_req = concept_messages.Type.IsAbstract.Req() - concept_method_req = concept_messages.Method.Req() - concept_method_req.type_isAbstract_req.CopyFrom(is_abstract_req) - return concept_method_req - - @staticmethod - def set_abstract(abstract): - set_abstract_req = concept_messages.Type.SetAbstract.Req() - assert type(abstract) == bool - set_abstract_req.abstract = abstract - concept_method_req = concept_messages.Method.Req() - concept_method_req.type_setAbstract_req.CopyFrom(set_abstract_req) - return concept_method_req - - @staticmethod - def instances(): - type_instances_req = concept_messages.Type.Instances.Iter.Req() - concept_method_req = concept_messages.Method.Iter.Req() - concept_method_req.type_instances_iter_req.CopyFrom(type_instances_req) - return concept_method_req - - @staticmethod - def keys(): - type_keys_req = concept_messages.Type.Keys.Iter.Req() - concept_method_req = concept_messages.Method.Iter.Req() - concept_method_req.type_keys_iter_req.CopyFrom(type_keys_req) - return concept_method_req - - @staticmethod - def attributes(): - type_attributes_req = concept_messages.Type.Attributes.Iter.Req() - concept_method_req = concept_messages.Method.Iter.Req() - concept_method_req.type_attributes_iter_req.CopyFrom(type_attributes_req) - return concept_method_req - - @staticmethod - def has(attribute_type_concept): - grpc_concept = RequestBuilder.ConceptMethod._concept_to_grpc_concept(attribute_type_concept) - has_req = concept_messages.Type.Has.Req() - has_req.attributeType.CopyFrom(grpc_concept) - concept_method_req = concept_messages.Method.Req() - concept_method_req.type_has_req.CopyFrom(has_req) - return concept_method_req - - @staticmethod - def unhas(attribute_type_concept): - grpc_concept = RequestBuilder.ConceptMethod._concept_to_grpc_concept(attribute_type_concept) - unhas_req = concept_messages.Type.Unhas.Req() - unhas_req.attributeType.CopyFrom(grpc_concept) - concept_method_req = concept_messages.Method.Req() - concept_method_req.type_unhas_req.CopyFrom(unhas_req) - return concept_method_req - - @staticmethod - def key(attribute_type_concept): - grpc_concept = RequestBuilder.ConceptMethod._concept_to_grpc_concept(attribute_type_concept) - key_req = concept_messages.Type.Key.Req() - key_req.attributeType.CopyFrom(grpc_concept) - concept_method_req = concept_messages.Method.Req() - concept_method_req.type_key_req.CopyFrom(key_req) - return concept_method_req - - @staticmethod - def unkey(attribute_type_concept): - grpc_concept = RequestBuilder.ConceptMethod._concept_to_grpc_concept(attribute_type_concept) - unkey_req = concept_messages.Type.Unkey.Req() - unkey_req.attributeType.CopyFrom(grpc_concept) - concept_method_req = concept_messages.Method.Req() - concept_method_req.type_unkey_req.CopyFrom(unkey_req) - return concept_method_req - - @staticmethod - def playing(): - playing_req = concept_messages.Type.Playing.Iter.Req() - concept_method_req = concept_messages.Method.Iter.Req() - concept_method_req.type_playing_iter_req.CopyFrom(playing_req) - return concept_method_req - - @staticmethod - def plays(role_concept): - grpc_concept = RequestBuilder.ConceptMethod._concept_to_grpc_concept(role_concept) - plays_req = concept_messages.Type.Plays.Req() - plays_req.role.CopyFrom(grpc_concept) - concept_method_req = concept_messages.Method.Req() - concept_method_req.type_plays_req.CopyFrom(plays_req) - return concept_method_req - - @staticmethod - def unplay(role_concept): - grpc_concept = RequestBuilder.ConceptMethod._concept_to_grpc_concept(role_concept) - unplay_req = concept_messages.Type.Unplay.Req() - unplay_req.role.CopyFrom(grpc_concept) - concept_method_req = concept_messages.Method.Req() - concept_method_req.type_unplay_req.CopyFrom(unplay_req) - return concept_method_req - - class EntityType(object): - """ Generates EntityType method messages """ - - @staticmethod - def create(): - create_req = concept_messages.EntityType.Create.Req() - concept_method_req = concept_messages.Method.Req() - concept_method_req.entityType_create_req.CopyFrom(create_req) - return concept_method_req - - class RelationType(object): - """ Generates RelationType method messages """ - - @staticmethod - def create(): - create_req = concept_messages.RelationType.Create.Req() - concept_method_req = concept_messages.Method.Req() - concept_method_req.relationType_create_req.CopyFrom(create_req) - return concept_method_req - - @staticmethod - def roles(): - roles_req = concept_messages.RelationType.Roles.Iter.Req() - concept_messages_req = concept_messages.Method.Iter.Req() - concept_messages_req.relationType_roles_iter_req.CopyFrom(roles_req) - return concept_messages_req - - @staticmethod - def relates(role_concept): - grpc_role_concept = RequestBuilder.ConceptMethod._concept_to_grpc_concept(role_concept) - relates_req = concept_messages.RelationType.Relates.Req() - relates_req.role.CopyFrom(grpc_role_concept) - concept_messages_req = concept_messages.Method.Req() - concept_messages_req.relationType_relates_req.CopyFrom(relates_req) - return concept_messages_req - - @staticmethod - def unrelate(role_concept): - grpc_role_concept = RequestBuilder.ConceptMethod._concept_to_grpc_concept(role_concept) - unrelate_req = concept_messages.RelationType.Unrelate.Req() - unrelate_req.role.CopyFrom(grpc_role_concept) - concept_messages_req = concept_messages.Method.Req() - concept_messages_req.relationType_unrelate_req.CopyFrom(unrelate_req) - return concept_messages_req - - class AttributeType(object): - """ Generates AttributeType method messages """ - - @staticmethod - def create(value, valuetype): - grpc_value_object = RequestBuilder.ConceptMethod.as_value_object(value, valuetype) - create_attr_req = concept_messages.AttributeType.Create.Req() - create_attr_req.value.CopyFrom(grpc_value_object) - concept_method_req = concept_messages.Method.Req() - concept_method_req.attributeType_create_req.CopyFrom(create_attr_req) - return concept_method_req - - @staticmethod - def attribute(value, valuetype): - grpc_value_object = RequestBuilder.ConceptMethod.as_value_object(value, valuetype) - attribute_req = concept_messages.AttributeType.Attribute.Req() - attribute_req.value.CopyFrom(grpc_value_object) - concept_method_req = concept_messages.Method.Req() - concept_method_req.attributeType_attribute_req.CopyFrom(attribute_req) - return concept_method_req - - @staticmethod - def value_type(): - valuetype_req = concept_messages.AttributeType.ValueType.Req() - concept_method_req = concept_messages.Method.Req() - concept_method_req.attributeType_valueType_req.CopyFrom(valuetype_req) - return concept_method_req - - @staticmethod - def get_regex(): - get_regex_req = concept_messages.AttributeType.GetRegex.Req() - concept_method_req = concept_messages.Method.Req() - concept_method_req.attributeType_getRegex_req.CopyFrom(get_regex_req) - return concept_method_req - - @staticmethod - def set_regex(regex): - set_regex_req = concept_messages.AttributeType.SetRegex.Req() - set_regex_req.regex = regex - concept_method_req = concept_messages.Method.Req() - concept_method_req.attributeType_setRegex_req.CopyFrom(set_regex_req) - return concept_method_req - - class Thing(object): - """ Generates Thing method messages """ - - @staticmethod - def is_inferred(): - is_inferred_req = concept_messages.Thing.IsInferred.Req() - concept_method_req = concept_messages.Method.Req() - concept_method_req.thing_isInferred_req.CopyFrom(is_inferred_req) - return concept_method_req - - @staticmethod - def type(): - type_req = concept_messages.Thing.Type.Req() - concept_method_req = concept_messages.Method.Req() - concept_method_req.thing_type_req.CopyFrom(type_req) - return concept_method_req - - @staticmethod - def attributes(attribute_types=[]): - """ Takes a list of AttributeType concepts to narrow attribute retrieval """ - attributes_req = concept_messages.Thing.Attributes.Iter.Req() - for attribute_type_concept in attribute_types: - grpc_attr_type_concept = RequestBuilder.ConceptMethod._concept_to_grpc_concept(attribute_type_concept) - attributes_req.attributeTypes.extend([grpc_attr_type_concept]) - concept_method_req = concept_messages.Method.Iter.Req() - concept_method_req.thing_attributes_iter_req.CopyFrom(attributes_req) - return concept_method_req - - @staticmethod - def relations(role_concepts=[]): - """ Takes a list of role concepts to narrow the relations retrieval """ - relations_req = concept_messages.Thing.Relations.Iter.Req() - for role_concept in role_concepts: - grpc_role_concept = RequestBuilder.ConceptMethod._concept_to_grpc_concept(role_concept) - # TODO this could use .add() if can be made to work... - relations_req.roles.extend([grpc_role_concept]) - concept_method_req = concept_messages.Method.Iter.Req() - concept_method_req.thing_relations_iter_req.CopyFrom(relations_req) - return concept_method_req - - @staticmethod - def roles(): - roles_req = concept_messages.Thing.Roles.Iter.Req() - concept_method_req = concept_messages.Method.Iter.Req() - concept_method_req.thing_roles_iter_req.CopyFrom(roles_req) - return concept_method_req - - @staticmethod - def keys(attribute_types=[]): - """ Takes a list of AttributeType concepts to narrow the key retrieval """ - keys_req = concept_messages.Thing.Keys.Iter.Req() - for attribute_type_concept in attribute_types: - grpc_attr_type_concept = RequestBuilder.ConceptMethod._concept_to_grpc_concept(attribute_type_concept) - keys_req.attributeTypes.extend([grpc_attr_type_concept]) - concept_method_req = concept_messages.Method.Iter.Req() - concept_method_req.thing_keys_iter_req.CopyFrom(keys_req) - return concept_method_req - - @staticmethod - def has(attribute_concept): - grpc_attribute_concept = RequestBuilder.ConceptMethod._concept_to_grpc_concept(attribute_concept) - has_req = concept_messages.Thing.Has.Req() - has_req.attribute.CopyFrom(grpc_attribute_concept) - concept_method_req = concept_messages.Method.Req() - concept_method_req.thing_has_req.CopyFrom(has_req) - return concept_method_req - - @staticmethod - def unhas(attribute_concept): - grpc_attribute_concept = RequestBuilder.ConceptMethod._concept_to_grpc_concept(attribute_concept) - unhas_req = concept_messages.Thing.Unhas.Req() - unhas_req.attribute.CopyFrom(grpc_attribute_concept) - concept_method_req = concept_messages.Method.Req() - concept_method_req.thing_unhas_req.CopyFrom(unhas_req) - return concept_method_req - - class Relation(object): - """ Generates Relation method messages """ - - @staticmethod - def role_players_map(): - role_players_map_req = concept_messages.Relation.RolePlayersMap.Iter.Req() - concept_method_req = concept_messages.Method.Iter.Req() - concept_method_req.relation_rolePlayersMap_iter_req.CopyFrom(role_players_map_req) - return concept_method_req - - @staticmethod - def role_players(roles=[]): - """ Retrieve concepts that can play the given roles """ - role_players_req = concept_messages.Relation.RolePlayers.Iter.Req() - for role_concept in roles: - grpc_role_concept = RequestBuilder.ConceptMethod._concept_to_grpc_concept(role_concept) - role_players_req.roles.extend([grpc_role_concept]) - concept_method_req = concept_messages.Method.Iter.Req() - concept_method_req.relation_rolePlayers_iter_req.CopyFrom(role_players_req) - return concept_method_req - - @staticmethod - def assign(role_concept, player_concept): - grpc_role_concept = RequestBuilder.ConceptMethod._concept_to_grpc_concept(role_concept) - grpc_player_concept = RequestBuilder.ConceptMethod._concept_to_grpc_concept(player_concept) - assign_req = concept_messages.Relation.Assign.Req() - assign_req.role.CopyFrom(grpc_role_concept) - assign_req.player.CopyFrom(grpc_player_concept) - concept_method_req = concept_messages.Method.Req() - concept_method_req.relation_assign_req.CopyFrom(assign_req) - return concept_method_req - - @staticmethod - def unassign(role_concept, player_concept): - grpc_role_concept = RequestBuilder.ConceptMethod._concept_to_grpc_concept(role_concept) - grpc_player_concept = RequestBuilder.ConceptMethod._concept_to_grpc_concept(player_concept) - unassign_req = concept_messages.Relation.Unassign.Req() - unassign_req.role.CopyFrom(grpc_role_concept) - unassign_req.player.CopyFrom(grpc_player_concept) - concept_method_req = concept_messages.Method.Req() - concept_method_req.relation_unassign_req.CopyFrom(unassign_req) - return concept_method_req - - class Attribute(object): - """ Generates Attribute method messages """ - - @staticmethod - def value(): - value_req = concept_messages.Attribute.Value.Req() - concept_method_req = concept_messages.Method.Req() - concept_method_req.attribute_value_req.CopyFrom(value_req) - return concept_method_req - - @staticmethod - def owners(): - owners_req = concept_messages.Attribute.Owners.Iter.Req() - concept_method_req = concept_messages.Method.Iter.Req() - concept_method_req.attribute_owners_iter_req.CopyFrom(owners_req) - return concept_method_req - - class Entity(object): - """ Empty implementation -- never create requests on Entity """ - pass diff --git a/grakn/service/Session/util/ResponseReader.py b/grakn/service/Session/util/ResponseReader.py deleted file mode 100644 index 84c5c583..00000000 --- a/grakn/service/Session/util/ResponseReader.py +++ /dev/null @@ -1,346 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -import datetime -from grakn.service.Session.util import enums -from grakn.service.Session.Concept import ConceptFactory -from grakn.exception.GraknError import GraknError -from six.moves import map - - -class ResponseReader(object): - - @staticmethod - def get_query_results(tx_service): - return lambda iterate_res: AnswerConverter.convert(tx_service, iterate_res.query_iter_res.answer) - - @staticmethod - def get_concept(tx_service, grpc_get_schema_concept): - which_one = grpc_get_schema_concept.WhichOneof("res") - if which_one == "concept": - grpc_concept = grpc_get_schema_concept.concept - return ConceptFactory.create_remote_concept(tx_service, grpc_concept) - elif which_one == "null": - return None - else: - raise GraknError("Unknown get_concept response: {0}".format(which_one)) - - @staticmethod - def get_schema_concept(tx_service, grpc_get_concept): - which_one = grpc_get_concept.WhichOneof("res") - if which_one == "schemaConcept": - grpc_concept = grpc_get_concept.schemaConcept - return ConceptFactory.create_remote_concept(tx_service, grpc_concept) - elif which_one == "null": - return None - else: - raise GraknError("Unknown get_schema_concept response: {0}".format(which_one)) - - @staticmethod - def get_attributes_by_value(tx_service): - return lambda iterate_res: ConceptFactory.create_remote_concept(tx_service, iterate_res.getAttributes_iter_res.attribute) - - @staticmethod - def put_entity_type(tx_service, grpc_put_entity_type): - return ConceptFactory.create_remote_concept(tx_service, grpc_put_entity_type.entityType) - - @staticmethod - def put_relation_type(tx_service, grpc_put_relation_type): - return ConceptFactory.create_remote_concept(tx_service, grpc_put_relation_type.relationType) - - @staticmethod - def put_attribute_type(tx_service, grpc_put_attribute_type): - return ConceptFactory.create_remote_concept(tx_service, grpc_put_attribute_type.attributeType) - - @staticmethod - def put_role(tx_service, grpc_put_role): - return ConceptFactory.create_remote_concept(tx_service, grpc_put_role.role) - - @staticmethod - def put_rule(tx_service, grpc_put_rule): - return ConceptFactory.create_remote_concept(tx_service, grpc_put_rule.rule) - - @staticmethod - def from_grpc_value_object(grpc_value_object): - whichone = grpc_value_object.WhichOneof('value') - # check the one is in the known ValueTypes - known_valuetypes = [e.name.lower() for e in enums.ValueType] - if whichone.lower() not in known_valuetypes: - raise GraknError("Unknown value object value key: {0}, not in {1}".format(whichone, known_valuetypes)) - if whichone == 'string': - return grpc_value_object.string - elif whichone == 'boolean': - return grpc_value_object.boolean - elif whichone == 'integer': - return grpc_value_object.integer - elif whichone == 'long': - return grpc_value_object.long - elif whichone == 'float': - return grpc_value_object.float - elif whichone == 'double': - return grpc_value_object.double - elif whichone == 'datetime': - epoch_ms_utc = grpc_value_object.datetime - local_datetime_utc = datetime.datetime.fromtimestamp(float(epoch_ms_utc)/1000.) - return local_datetime_utc - else: - raise GraknError("Unknown valuetype in enum but not handled in from_grpc_value_object") - - @staticmethod - def from_grpc_value_type_res(grpc_value_type_res): - whichone = grpc_value_type_res.WhichOneof('res') - if whichone == 'valueType': - # iterate over enum ValueType enum to find matching data type - for e in enums.ValueType: - if e.value == grpc_value_type_res.valueType: - return e - else: - # loop exited normally - raise GraknError("Reported valuetype NOT in enum: {0}".format(grpc_value_type_res.valueType)) - elif whichone == 'null': - return None - else: - raise GraknError("Unknown valuetype response for AttributeType: {0}".format(whichone)) - - # --- concept method helpers --- - - @staticmethod - def create_explanation(tx_service, grpc_explanation_res): - """ Convert gRPC explanation response to explanation object """ - grpc_list_of_concept_maps = grpc_explanation_res.explanation - native_list_of_concept_maps = [] - for grpc_concept_map in grpc_list_of_concept_maps: - native_list_of_concept_maps.append(AnswerConverter._create_concept_map(tx_service, grpc_concept_map)) - rule = ConceptFactory.create_local_concept(grpc_explanation_res.rule) - if len(rule.id) == 0: - rule = None - return Explanation(native_list_of_concept_maps, rule) - - -class Explanation(object): - - def __init__(self, list_of_concept_maps, rule): - self._concept_maps_list = list_of_concept_maps - self._rule = rule - - def get_answers(self): - """ Return answers this explanation is dependent on""" - # note that concept_maps are subtypes of Answer - return self._concept_maps_list - - def get_rule(self): - """ return the rule that is being explained, if there is one """ - return self._rule - - -# ----- Different types of answers ----- - -class AnswerGroup(object): - - def __init__(self, owner_concept, answer_list): - self._owner_concept = owner_concept - self._answer_list = answer_list - - def get(self): - return self - - def owner(self): - return self._owner_concept - - def answers(self): - return self._answer_list - - -class ConceptMap(object): - - def __init__(self, concept_map, query_pattern, has_explanation, tx_service): - self._concept_map = concept_map - self._has_explanation = has_explanation - self._query_pattern = query_pattern - self._tx_service = tx_service - - def get(self, var=None): - """ Get the indicated variable's Concept from the map or this ConceptMap """ - if var is None: - return self - else: - if var not in self._concept_map: - # TODO specialize exception - raise GraknError("Variable {0} is not in the ConceptMap".format(var)) - return self._concept_map[var] - - def query_pattern(self): - return self._query_pattern - - def has_explanation(self): - return self._has_explanation - - def explanation(self): - if self._has_explanation: - return self._tx_service.explanation(self) - else: - raise GraknError("Explanation not available on concept map: " + str(self)) - - def map(self): - """ Get the map from Variable (str) to Concept objects """ - return self._concept_map - - def vars(self): - """ Get a set of vars in the map """ - return set(self._concept_map.keys()) - - def contains_var(self, var): - """ Check whether the map contains the var """ - return var in self._concept_map - - def is_empty(self): - """ Check if the variable map is empty """ - return len(self._concept_map) == 0 - - -class ConceptList(object): - - def __init__(self, concept_id_list): - self._concept_id_list = concept_id_list - - def list(self): - """ Get the list of concept IDs """ - return self._concept_id_list - - -class ConceptSet(object): - - def __init__(self, concept_id_set): - self._concept_id_set = concept_id_set - __init__.__annotations__ = {'_concept_id_set': 'List[str]'} - - def set(self): - """ Return the set of Concept IDs within this ConceptSet """ - return self._concept_id_set - - -class ConceptSetMeasure(ConceptSet): - - def __init__(self, concept_id_set, number): - super(ConceptSetMeasure, self).__init__(concept_id_set) - self._measurement = number - __init__.__annotations__ = {'_measurement': float} - - def measurement(self): - return self._measurement - - -class Value(object): - - def __init__(self, number): - self._number = number - __init__.__annotations__ = {'number': float} - - def number(self): - """ Get as number (float or int) """ - return self._number - - -class Void(object): - def __init__(self, message): - self._message = message - __init__.__annotations__ = {'message': str} - - def message(self): - """ Get the message on this Void answer type """ - return self._message - - -class AnswerConverter(object): - """ Static methods to convert answers into Answer objects """ - - @staticmethod - def convert(tx_service, grpc_answer): - which_one = grpc_answer.WhichOneof('answer') - - if which_one == 'conceptMap': - return AnswerConverter._create_concept_map(tx_service, grpc_answer.conceptMap) - elif which_one == 'answerGroup': - return AnswerConverter._create_answer_group(tx_service, grpc_answer.answerGroup) - elif which_one == 'conceptList': - return AnswerConverter._create_concept_list(tx_service, grpc_answer.conceptList) - elif which_one == 'conceptSet': - return AnswerConverter._create_concept_set(tx_service, grpc_answer.conceptSet) - elif which_one == 'conceptSetMeasure': - return AnswerConverter._create_concept_set_measure(tx_service, grpc_answer.conceptSetMeasure) - elif which_one == 'value': - return AnswerConverter._create_value(tx_service, grpc_answer.value) - elif which_one == 'void': - return AnswerConverter._create_void(tx_service, grpc_answer.void) - else: - raise GraknError('Unknown gRPC Answer.answer message type: {0}'.format(which_one)) - - @staticmethod - def _create_concept_map(tx_service, grpc_concept_map_msg): - """ Create a Concept Dictionary from the grpc response """ - var_concept_map = grpc_concept_map_msg.map - answer_map = {} - for (variable, grpc_concept) in var_concept_map.items(): - answer_map[variable] = ConceptFactory.create_local_concept(grpc_concept) - - query_pattern = grpc_concept_map_msg.pattern - has_explanation = grpc_concept_map_msg.hasExplanation - - return ConceptMap(answer_map, query_pattern, has_explanation, tx_service) - - @staticmethod - def _create_answer_group(tx_service, grpc_answer_group): - grpc_owner_concept = grpc_answer_group.owner - owner_concept = ConceptFactory.create_local_concept(grpc_owner_concept) - grpc_answers = list(grpc_answer_group.answers) - answer_list = [AnswerConverter.convert(tx_service, grpc_answer) for grpc_answer in grpc_answers] - return AnswerGroup(owner_concept, answer_list) - - @staticmethod - def _create_concept_list(tx_service, grpc_concept_list_msg): - ids_list = list(grpc_concept_list_msg.list.ids) - return ConceptList(ids_list) - - @staticmethod - def _create_concept_set(tx_service, grpc_concept_set_msg): - ids_set = set(grpc_concept_set_msg.set.ids) - return ConceptSet(ids_set) - - @staticmethod - def _create_concept_set_measure(tx_service, grpc_concept_set_measure): - concept_ids = list(grpc_concept_set_measure.set.ids) - number = grpc_concept_set_measure.measurement.value - return ConceptSetMeasure(concept_ids, AnswerConverter._number_string_to_native(number)) - - @staticmethod - def _create_value(tx_service, grpc_value_msg): - number = grpc_value_msg.number.value - return Value(AnswerConverter._number_string_to_native(number)) - - @staticmethod - def _create_void(tx_service, grpc_void): - """ Convert grpc Void message into an object """ - return Void(grpc_void.message) - - @staticmethod - def _number_string_to_native(number): - try: - return int(number) - except ValueError: - return float(number) diff --git a/grakn/service/Session/util/enums.py b/grakn/service/Session/util/enums.py deleted file mode 100644 index d52aea72..00000000 --- a/grakn/service/Session/util/enums.py +++ /dev/null @@ -1,40 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -from enum import Enum -import grakn_protocol.session.Session_pb2 as SessionMessages -import grakn_protocol.session.Concept_pb2 as ConceptMessages - - -class TxType(Enum): - READ = SessionMessages.Transaction.Type.Value('READ') - WRITE = SessionMessages.Transaction.Type.Value('WRITE') - BATCH = SessionMessages.Transaction.Type.Value('BATCH') - - -VALUE_TYPE_map = {} - -class ValueType(Enum): - STRING = ConceptMessages.AttributeType.VALUE_TYPE.Value('STRING') - BOOLEAN = ConceptMessages.AttributeType.VALUE_TYPE.Value('BOOLEAN') - INTEGER = ConceptMessages.AttributeType.VALUE_TYPE.Value('INTEGER') - LONG = ConceptMessages.AttributeType.VALUE_TYPE.Value('LONG') - FLOAT = ConceptMessages.AttributeType.VALUE_TYPE.Value('FLOAT') - DOUBLE = ConceptMessages.AttributeType.VALUE_TYPE.Value('DOUBLE') - DATETIME = ConceptMessages.AttributeType.VALUE_TYPE.Value('DATETIME') diff --git a/requirements.txt b/requirements.txt index 9849d529..c3fcc8f3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,24 @@ -grpcio==1.24.1,<2 +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +--extra-index-url https://repo.grakn.ai/repository/pypi-snapshot/simple +graknprotocol==0.0.0-6bf8c601ecd57a2869cde56c17eec8784a9a2804 +grpcio==1.33.2 protobuf==3.6.1 six>=1.11.0 diff --git a/test/BUILD b/test/BUILD new file mode 100644 index 00000000..709acfda --- /dev/null +++ b/test/BUILD @@ -0,0 +1,92 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +load("@graknlabs_common//test/server:rules.bzl", "native_grakn_artifact") +load("@graknlabs_dependencies//tool/checkstyle:rules.bzl", "checkstyle_test") +load("@rules_python//python:defs.bzl", "py_library", "py_test") + +native_grakn_artifact( + name = "native-grakn-artifact", + mac_artifact = "@graknlabs_grakn_core_artifact_mac//file", + linux_artifact = "@graknlabs_grakn_core_artifact_linux//file", + windows_artifact = "@graknlabs_grakn_core_artifact_windows//file", + visibility = ["//test:__subpackages__"], +) + +py_test( + name = "test_concept", + srcs = [ + "integration/base.py", + "integration/test_concept.py" + ], + deps = [ + "//:client_python", + ], + data = [":native-grakn-artifact"], + args = ["$(location :native-grakn-artifact)"], + python_version = "PY3" +) + +py_test( + name = "test_connection", + srcs = [ + "integration/base.py", + "integration/test_connection.py" + ], + deps = [ + "//:client_python", + ], + data = [":native-grakn-artifact"], + args = ["$(location :native-grakn-artifact)"], + python_version = "PY3" +) + +py_test( + name = "test_query", + srcs = [ + "integration/base.py", + "integration/test_query.py" + ], + deps = [ + "//:client_python", + ], + size = "large", + data = [":native-grakn-artifact"], + args = ["$(location :native-grakn-artifact)"], + python_version = "PY3" +) + +test_suite( + name = "test_integration", + tests = [ + ":test_concept", + ":test_connection", + ":test_query", + ] +) + +checkstyle_test( + name = "checkstyle", + include = glob([ + "*", + "deployment/*", + "integration/*", + ]), + license_type = "apache", +) diff --git a/test/deployment/requirements.txt b/test/deployment/requirements.txt new file mode 100644 index 00000000..267cebca --- /dev/null +++ b/test/deployment/requirements.txt @@ -0,0 +1,20 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +https://repo.grakn.ai/repository/pypi-snapshot-group/packages/grakn-client/CLIENT_PYTHON_VERSION_MARKER/grakn-client-CLIENT_PYTHON_VERSION_MARKER.tar.gz \ No newline at end of file diff --git a/tests/deployment/test.py b/test/deployment/test.py similarity index 63% rename from tests/deployment/test.py rename to test/deployment/test.py index eb30a0c3..f2b0312c 100644 --- a/tests/deployment/test.py +++ b/test/deployment/test.py @@ -1,8 +1,26 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + from unittest import TestCase from grakn.client import GraknClient - class PythonApplicationTest(TestCase): """ Very basic tests to ensure no error occur when performing simple operations with the test grakn-client distribution""" diff --git a/tests/integration/base.py b/test/integration/base.py similarity index 70% rename from tests/integration/base.py rename to test/integration/base.py index d34245a4..cf3e412a 100755 --- a/tests/integration/base.py +++ b/test/integration/base.py @@ -28,6 +28,7 @@ import tarfile +# TODO: update to work with Grakn 2.0 class GraknServer(object): DISTRIBUTION_LOCATION = sys.argv.pop() @@ -35,18 +36,18 @@ def __init__(self): self.__distribution_root_dir = None self.__unpacked_dir = None - def __enter__(self): - if not self.__unpacked_dir: - self._unpack() - sp.check_call([ - 'grakn', 'server', 'start' - ], cwd=os.path.join(self.__unpacked_dir, self.__distribution_root_dir)) + def __enter__(self): pass + # if not self.__unpacked_dir: + # self._unpack() + # sp.check_call([ + # 'grakn', 'server' + # ], cwd=os.path.join(self.__unpacked_dir, self.__distribution_root_dir)) - def __exit__(self, exc_type, exc_val, exc_tb): - sp.check_call([ - 'grakn', 'server', 'stop' - ], cwd=os.path.join(self.__unpacked_dir, self.__distribution_root_dir)) - shutil.rmtree(self.__unpacked_dir) + def __exit__(self, exc_type, exc_val, exc_tb): pass + # sp.check_call([ + # 'grakn', 'server', 'stop' + # ], cwd=os.path.join(self.__unpacked_dir, self.__distribution_root_dir)) + # shutil.rmtree(self.__unpacked_dir) def _unpack(self): self.__unpacked_dir = tempfile.mkdtemp(prefix='grakn') @@ -55,13 +56,13 @@ def _unpack(self): self.__distribution_root_dir = os.path.commonpath(tf.getnames()[1:]) -class test_Base(TestCase): +class test_base(TestCase): """ Sets up DB for use in tests """ @classmethod def setUpClass(cls): - super(test_Base, cls).setUpClass() + super(test_base, cls).setUpClass() @classmethod def tearDownClass(cls): - super(test_Base, cls).tearDownClass() + super(test_base, cls).tearDownClass() diff --git a/test/integration/test_concept.py b/test/integration/test_concept.py new file mode 100644 index 00000000..51bda174 --- /dev/null +++ b/test/integration/test_concept.py @@ -0,0 +1,287 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +import unittest +from datetime import datetime + +from grakn.client import GraknClient, SessionType, TransactionType, ValueType +from grakn.common.exception import GraknClientException +from test.integration.base import test_base, GraknServer + + +class TestConcept(test_base): + @classmethod + def setUpClass(cls): + super(TestConcept, cls).setUpClass() + global client + client = GraknClient() + + @classmethod + def tearDownClass(cls): + super(TestConcept, cls).tearDownClass() + client.close() + + def setUp(self): + if "grakn" in client.databases().all(): + client.databases().delete("grakn") + client.databases().create("grakn") + + def test_get_supertypes(self): + with client.session("grakn", SessionType.SCHEMA) as session: + with session.transaction(TransactionType.WRITE) as tx: + lion = tx.concepts().put_entity_type("lion") + for lion_supertype in lion.as_remote(tx).get_supertypes(): + print(str(lion_supertype) + " is a supertype of 'lion'") + + def test_streaming_operation_on_closed_tx(self): + with client.session("grakn", SessionType.SCHEMA) as session: + with session.transaction(TransactionType.WRITE) as tx: + lion = tx.concepts().put_entity_type("lion") + tx.close() + try: + for _ in lion.as_remote(tx).get_supertypes(): + self.fail() + self.fail() + except GraknClientException: + pass + + def test_invalid_streaming_operation(self): + with client.session("grakn", SessionType.SCHEMA) as session: + with session.transaction(TransactionType.WRITE) as tx: + lion = tx.concepts().put_entity_type("lion") + lion._label = "lizard" + try: + for _ in lion.as_remote(tx).get_supertypes(): + self.fail() + self.fail() + except GraknClientException: + pass + + def test_get_many_instances(self): + with client.session("grakn", SessionType.SCHEMA) as session: + with session.transaction(TransactionType.WRITE) as tx: + goldfish_type = tx.concepts().put_entity_type("goldfish") + tx.commit() + with client.session("grakn", SessionType.DATA) as session: + with session.transaction(TransactionType.WRITE) as tx: + for _ in range(100): + goldfish_type.as_remote(tx).create() + goldfish_count = sum(1 for _ in goldfish_type.as_remote(tx).get_instances()) + print("There are " + str(goldfish_count) + " goldfish.") + + def test_stone_lions(self): + # SCHEMA OPERATIONS + with client.session("grakn", SessionType.SCHEMA) as session: + with session.transaction(TransactionType.WRITE) as tx: + lion = tx.concepts().put_entity_type("lion") + tx.commit() + print("put_entity_type - SUCCESS") + + with session.transaction(TransactionType.WRITE) as tx: + lionFamily = tx.concepts().put_relation_type("lion-family") + lionFamily.as_remote(tx).set_relates("lion-cub") + lionCub = next(lionFamily.as_remote(tx).get_relates()) + lion.as_remote(tx).set_plays(lionCub) + tx.commit() + print("put_relation_type / set_relates / set_plays - SUCCESS") + + with session.transaction(TransactionType.WRITE) as tx: + maneSize = tx.concepts().put_attribute_type("mane-size", ValueType.LONG) + lion.as_remote(tx).set_owns(maneSize) + tx.commit() + print("commit attribute type + owns - SUCCESS") + + with session.transaction(TransactionType.WRITE) as tx: + stoneLion = tx.concepts().put_entity_type("stone-lion") + stoneLion.as_remote(tx).set_supertype(lion) + tx.commit() + print("set supertype - SUCCESS") + + with session.transaction(TransactionType.READ) as tx: + supertypeOfLion = lion.as_remote(tx).get_supertype() + tx.close() + print("get supertype - SUCCESS - the supertype of 'lion' is '" + supertypeOfLion.get_label() + "'") + + with session.transaction(TransactionType.READ) as tx: + supertypesOfStoneLion = list(map(lambda x: x.get_label(), stoneLion.as_remote(tx).get_supertypes())) + print("get supertypes - SUCCESS - the supertypes of 'stone-lion' are " + str(supertypesOfStoneLion)) + + with session.transaction(TransactionType.READ) as tx: + subtypesOfLion = list(map(lambda x: x.get_label(), lion.as_remote(tx).get_subtypes())) + print("get subtypes - SUCCESS - the subtypes of 'lion' are " + str(subtypesOfLion)) + + with session.transaction(TransactionType.WRITE) as tx: + monkey = tx.concepts().put_entity_type("monkey") + monkey.as_remote(tx).set_label("orangutan") + newLabel = tx.concepts().get_entity_type("orangutan").get_label() + tx.rollback() + assert newLabel == "orangutan" + print("set label - SUCCESS - 'monkey' has been renamed to '" + newLabel + "'.") + + with session.transaction(TransactionType.WRITE) as tx: + whale = tx.concepts().put_entity_type("whale") + whale.as_remote(tx).set_abstract() + isAbstractAfterSet = whale.as_remote(tx).is_abstract() + assert isAbstractAfterSet + print("set abstract - SUCCESS - 'whale' " + ("is" if isAbstractAfterSet else "is not") + " abstract.") + whale.as_remote(tx).unset_abstract() + isAbstractAfterUnset = whale.as_remote(tx).is_abstract() + assert not isAbstractAfterUnset + tx.rollback() + print("unset abstract - SUCCESS - 'whale' " + ("is still" if isAbstractAfterUnset else "is no longer") + " abstract.") + + with session.transaction(TransactionType.WRITE) as tx: + parentship = tx.concepts().put_relation_type("parentship") + parentship.as_remote(tx).set_relates("parent") + fathership = tx.concepts().put_relation_type("fathership") + fathership.as_remote(tx).set_supertype(parentship) + fathership.as_remote(tx).set_relates("father", "parent") + person = tx.concepts().put_entity_type("person") + parent = parentship.as_remote(tx).get_relates("parent") + person.as_remote(tx).set_plays(parent) + man = tx.concepts().put_entity_type("man") + man.as_remote(tx).set_supertype(person) + father = fathership.as_remote(tx).get_relates("father") + man.as_remote(tx).set_plays(father, parent) + playingRoles = list(map(lambda role: role.get_scoped_label(), man.as_remote(tx).get_plays())) + roleplayers = list(map(lambda player: player.get_label(), father.as_remote(tx).get_players())) + tx.commit() + assert "fathership:father" in playingRoles + assert "man" in roleplayers + print("get/set relates/plays/players, overriding a super-role - SUCCESS - 'man' plays " + str(playingRoles) + "; 'fathership:father' is played by " + str(roleplayers)) + + with session.transaction(TransactionType.WRITE) as tx: + email = tx.concepts().put_attribute_type("email", ValueType.STRING) + email.as_remote(tx).set_abstract() + workEmail = tx.concepts().put_attribute_type("work-email", ValueType.STRING) + workEmail.as_remote(tx).set_supertype(email) + age = tx.concepts().put_attribute_type("age", ValueType.LONG) + assert age.is_long() + person.as_remote(tx).set_abstract() + person.as_remote(tx).set_owns(attribute_type=email, is_key=True) + person.as_remote(tx).set_owns(attribute_type=age, is_key=False) + lion.as_remote(tx).set_owns(attribute_type=age) + customer = tx.concepts().put_entity_type("customer") + customer.as_remote(tx).set_supertype(person) + customer.as_remote(tx).set_owns(attribute_type=workEmail, is_key=True, overridden_type=email) + ownedAttributes = list(map(lambda x: x.get_label(), customer.as_remote(tx).get_owns())) + ownedKeys = list(map(lambda x: x.get_label(), customer.as_remote(tx).get_owns(keys_only=True))) + ownedDateTimes = list(map(lambda x: x.get_label(), customer.as_remote(tx).get_owns(ValueType.DATETIME, keys_only=False))) + tx.commit() + assert len(ownedAttributes) == 2 + assert len(ownedKeys) == 1 + assert len(ownedDateTimes) == 0 + print("get/set owns, overriding a super-attribute - SUCCESS - 'customer' owns " + str(ownedAttributes) + ", " + "of which " + str(ownedKeys) + " are keys, and " + str(ownedDateTimes) + " are datetimes") + + with session.transaction(TransactionType.WRITE) as tx: + person.as_remote(tx).unset_owns(age) + person.as_remote(tx).unset_plays(parent) + fathership.as_remote(tx).unset_relates("father") + personOwns = list(map(lambda x: x.get_label(), person.as_remote(tx).get_owns())) + personPlays = list(map(lambda x: x.get_label(), person.as_remote(tx).get_plays())) + fathershipRelates = list(map(lambda x: x.get_label(), fathership.as_remote(tx).get_relates())) + tx.rollback() + assert "age" not in personOwns + assert "parent" not in personPlays + assert "father" not in fathershipRelates + print("unset owns/plays/relates - SUCCESS - 'person' owns " + str(personOwns) + ", " + "'person' plays " + str(personPlays) + ", 'fathership' relates " + str(fathershipRelates)) + + with session.transaction(TransactionType.WRITE) as tx: + password = tx.concepts().put_attribute_type("password", ValueType.STRING) + shoeSize = tx.concepts().put_attribute_type("shoe-size", ValueType.LONG) + volume = tx.concepts().put_attribute_type("volume", ValueType.DOUBLE) + isAlive = tx.concepts().put_attribute_type("is-alive", ValueType.BOOLEAN) + startDate = tx.concepts().put_attribute_type("start-date", ValueType.DATETIME) + tx.commit() + print("put all 5 attribute value types - SUCCESS - password is a " + password.get_value_type().name + ", shoe-size is a " + shoeSize.get_value_type().name + ", " + "volume is a " + volume.get_value_type().name + ", is-alive is a " + isAlive.get_value_type().name + " and start-date is a " + startDate.get_value_type().name) + + with session.transaction(TransactionType.WRITE) as tx: + tx.logic().put_rule("septuagenarian-rule", "{$x isa person;}", "$x has age 70") + tx.commit() + print("put rule - SUCCESS") + + # DATA OPERATIONS + with client.session("grakn", SessionType.DATA) as session: + with session.transaction(TransactionType.WRITE) as tx: + for _ in range(10): stoneLion.as_remote(tx).create() + lions = list(lion.as_remote(tx).get_instances()) + firstLion = lions[0] + isInferred = firstLion.as_remote(tx).is_inferred() + lionType = firstLion.as_remote(tx).get_type() + age42 = age.as_remote(tx).put(42) + firstLion.as_remote(tx).set_has(age42) + firstLionAttrs = list(map(lambda x: x.get_value(), firstLion.as_remote(tx).get_has())) + assert len(firstLionAttrs) == 1 + assert firstLionAttrs[0] == 42 + firstLionAges = list(map(lambda x: x.get_value(), firstLion.as_remote(tx).get_has(age))) + assert len(firstLionAges) == 1 + assert firstLionAges[0] == 42 + firstLionWorkEmails = list(map(lambda x: x.get_value(), firstLion.as_remote(tx).get_has(workEmail))) + assert len(firstLionWorkEmails) == 0 + firstFamily = lionFamily.as_remote(tx).create() + firstFamily.as_remote(tx).add_player(lionCub, firstLion) + firstLionPlaying = list(map(lambda x: x.get_scoped_label(), firstLion.as_remote(tx).get_plays())) + assert len(firstLionPlaying) == 1 + assert firstLionPlaying[0] == "lion-family:lion-cub" + firstLionRelations = list(firstLion.as_remote(tx).get_relations()) + assert len(firstLionRelations) == 1 + firstLionFatherRelations = list(firstLion.as_remote(tx).get_relations([father])) + assert len(firstLionFatherRelations) == 0 + tx.commit() + assert len(lions) == 10 + assert not isInferred + print("Thing methods - SUCCESS - There are " + str(len(lions)) + " lions.") + assert lionType.get_label() == "stone-lion" + print("getType - SUCCESS - After looking more closely, it turns out that there are " + str(len(lions)) + " stone lions.") + + with session.transaction(TransactionType.WRITE) as tx: + firstLionFamily = next(lionFamily.as_remote(tx).get_instances()) + firstLion = next(firstLionFamily.as_remote(tx).get_players()) + firstLionFamily2 = next(firstLion.as_remote(tx).get_relations()) + assert firstLionFamily2 + players = list(firstLionFamily.as_remote(tx).get_players()) + assert len(players) == 1 + lionCubPlayers = list(firstLionFamily.as_remote(tx).get_players([lionCub])) + assert len(lionCubPlayers) == 1 + playersByRoleType = firstLionFamily.as_remote(tx).get_players_by_role_type().items() + (firstRole, firstPlayer) = next(iter(playersByRoleType)) + assert firstRole.get_scoped_label() == "lion-family:lion-cub" + firstLionFamily.as_remote(tx).remove_player(lionCub, firstLion) + lionFamilyCleanedUp = firstLionFamily.as_remote(tx).is_deleted() + assert(lionFamilyCleanedUp) + tx.rollback() + print("Relation methods - SUCCESS") + + with session.transaction(TransactionType.WRITE) as tx: + passwordAttr = password.as_remote(tx).put("rosebud") + shoeSizeAttr = shoeSize.as_remote(tx).put(9) + volumeAttr = volume.as_remote(tx).put(1.618) + isAliveAttr = isAlive.as_remote(tx).put(bool("hopefully")) + startDateAttr = startDate.as_remote(tx).put(datetime.now()) + tx.commit() + print("put 5 different types of attributes - SUCCESS - password is " + passwordAttr.get_value() + ", shoe-size is " + str(shoeSizeAttr.get_value()) + ", " + "volume is " + str(volumeAttr.get_value()) + ", is-alive is " + str(isAliveAttr.get_value()) + " and start-date is " + str(startDateAttr.get_value())) + + +if __name__ == "__main__": + with GraknServer(): + unittest.main(verbosity=2) diff --git a/test/integration/test_connection.py b/test/integration/test_connection.py new file mode 100644 index 00000000..3770fdf6 --- /dev/null +++ b/test/integration/test_connection.py @@ -0,0 +1,61 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +import unittest +from grakn.client import GraknClient, SessionType, TransactionType +from test.integration.base import test_base, GraknServer + + +class TestConnection(test_base): + @classmethod + def setUpClass(cls): + super(TestConnection, cls).setUpClass() + global client + client = GraknClient() + + @classmethod + def tearDownClass(cls): + super(TestConnection, cls).tearDownClass() + global client + client.close() + + def test_database(self): + dbs = client.databases().all() + if "grakn" in dbs: + client.databases().delete("grakn") + client.databases().create("grakn") + dbs = client.databases().all() + self.assertTrue("grakn" in dbs) + + def test_session(self): + if "grakn" not in client.databases().all(): + client.databases().create("grakn") + session = client.session("grakn", SessionType.SCHEMA) + session.close() + + def test_transaction(self): + if "grakn" not in client.databases().all(): + client.databases().create("grakn") + with client.session("grakn", SessionType.SCHEMA) as session: + with session.transaction(TransactionType.WRITE) as tx: + pass + +if __name__ == "__main__": + with GraknServer(): + unittest.main(verbosity=2) diff --git a/tests/integration/test_grakn.py b/test/integration/test_grakn.py similarity index 96% rename from tests/integration/test_grakn.py rename to test/integration/test_grakn.py index fb8461d1..75283359 100644 --- a/tests/integration/test_grakn.py +++ b/test/integration/test_grakn.py @@ -20,14 +20,14 @@ import unittest import uuid import grakn -from grakn.client import GraknClient, ValueType, Transaction -from grakn.exception.GraknError import GraknError -from grakn.service.Session.util.ResponseReader import Value +from grakn import GraknClient, ValueType, Transaction +from grakn import GraknError -from tests.integration.base import test_Base, GraknServer +from test.integration.base import test_base, GraknServer -class test_client_PreDbSetup(test_Base): +# TODO: we should ensure that all these tests are migrated to BDD +class test_client_PreDbSetup(test_base): """ Tests Database interactions *before* anything needs to be inserted/created """ # --- Test grakn client instantiation for one URI --- @@ -66,14 +66,14 @@ def test_client_session_valid_keyspace(self): """ Test OK uri and keyspace """ a_inst = GraknClient('localhost:48555') a_session = a_inst.session('test') - self.assertIsInstance(a_session, grakn.client.Session) + self.assertIsInstance(a_session, grakn.rpc.Session) tx = a_session.transaction().read() tx.close() a_session.close() # test the `with` statement with a_inst.session('test') as session: - self.assertIsInstance(session, grakn.client.Session) + self.assertIsInstance(session, grakn.rpc.Session) tx = session.transaction().read() tx.close() @@ -103,7 +103,7 @@ def test_client_tx_valid_enum(self): client = GraknClient('localhost:48555') a_session = client.session('test') tx = a_session.transaction().read() - self.assertIsInstance(tx, grakn.client.Transaction) + self.assertIsInstance(tx, grakn.rpc.Transaction) client.close() def test_client_tx_invalid_enum(self): @@ -117,13 +117,13 @@ def test_client_tx_invalid_enum(self): client = None session = None -class test_client_Base(test_Base): +class test_client_base(test_base): """ Sets up DB for use in tests """ @classmethod def setUpClass(cls): """ Make sure we have some sort of schema and data in DB, only done once """ - super(test_client_Base, cls).setUpClass() + super(test_client_base, cls).setUpClass() global client, session client = GraknClient("localhost:48555") @@ -153,7 +153,7 @@ def setUpClass(cls): @classmethod def tearDownClass(cls): - super(test_client_Base, cls).tearDownClass() + super(test_client_base, cls).tearDownClass() global client, session session.close() @@ -171,7 +171,7 @@ def cleanupTransaction(self, tx): -class test_Transaction(test_client_Base): +class test_Transaction(test_client_base): """ Class for testing transaction methods, eg query, put attribute type... """ # --- query tests --- diff --git a/test/integration/test_query.py b/test/integration/test_query.py new file mode 100644 index 00000000..6971d286 --- /dev/null +++ b/test/integration/test_query.py @@ -0,0 +1,61 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +import unittest +from grakn.client import GraknClient, SessionType, TransactionType +from test.integration.base import test_base, GraknServer + + +class TestQuery(test_base): + @classmethod + def setUpClass(cls): + global client + client = GraknClient() + + @classmethod + def tearDownClass(cls): + client.close() + + def setUp(self): + if "grakn" not in client.databases().all(): + client.databases().create("grakn") + + def test_define_and_undef_relation_type(self): + with client.session("grakn", SessionType.SCHEMA) as session: + with session.transaction(TransactionType.WRITE) as tx: + tx.query().define("define lionfight sub relation, relates victor, relates loser;") + lionfight_type = tx.concepts().get_type("lionfight") + print(lionfight_type._label) + tx.query().undefine("undefine lionfight sub relation;") + tx.commit() + + def test_insert_some_entities(self): + with client.session("grakn", SessionType.SCHEMA) as session: + with session.transaction(TransactionType.WRITE) as tx: + tx.query().define("define lion sub entity;") + tx.commit() + with client.session("grakn", SessionType.DATA) as session: + with session.transaction(TransactionType.WRITE) as tx: + for answer in tx.query().insert("insert $a isa lion; $b isa lion; $c isa lion;"): + print(answer) + + +if __name__ == "__main__": + with GraknServer(): + unittest.main(verbosity=2) diff --git a/tests/deployment/requirements.txt b/tests/deployment/requirements.txt deleted file mode 100644 index 9eee3011..00000000 --- a/tests/deployment/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -https://repo.grakn.ai/repository/pypi-snapshot-group/packages/grakn-client/CLIENT_PYTHON_VERSION_MARKER/grakn-client-CLIENT_PYTHON_VERSION_MARKER.tar.gz \ No newline at end of file diff --git a/tests/integration/test_answer.py b/tests/integration/test_answer.py deleted file mode 100644 index 8472e932..00000000 --- a/tests/integration/test_answer.py +++ /dev/null @@ -1,296 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -import unittest -from grakn.client import GraknClient -from grakn.service.Session.util.ResponseReader import Value, ConceptList, ConceptSet, ConceptSetMeasure, AnswerGroup, \ - Void -from tests.integration.base import test_Base, GraknServer - -client = None - - -class test_Answers(test_Base): - @classmethod - def setUpClass(cls): - global client - client = GraknClient("localhost:48555") - - @classmethod - def tearDownClass(cls): - client.close() - - @staticmethod - def _build_parentship(tx): - """ Helper to set up some state to test answers in a tx/keyspace """ - parentship_type = tx.put_relation_type("parentship") - parentship = parentship_type.create() - parent_role = tx.put_role("parent") - child_role = tx.put_role("child") - parentship_type.relates(parent_role) - parentship_type.relates(child_role) - person_type = tx.put_entity_type("person") - person_type.plays(parent_role) - person_type.plays(child_role) - parent = person_type.create() - child = person_type.create() - parentship.assign(child_role, child) - parentship.assign(parent_role, parent) - tx.commit() # closes the tx - return {'child': child.id, 'parent': parent.id, 'parentship': parentship.id} - - - def test_shortest_path_answer_ConceptList(self): - """ Test shortest path which returns a ConceptList """ - with client.session("shortestpath") as local_session: - tx = local_session.transaction().write() - parentship_map = test_Answers._build_parentship(tx) # this closes the tx - tx = local_session.transaction().write() - result = tx.query('compute path from {0}, to {1};'.format(parentship_map['parent'], parentship_map['child'])) - answer = next(result) - self.assertIsInstance(answer, ConceptList) - self.assertEqual(len(answer.list()), 3) - self.assertTrue(parentship_map['parent'] in answer.list()) - self.assertTrue(parentship_map['child'] in answer.list()) - self.assertTrue(parentship_map['parentship'] in answer.list()) - tx.close() - client.keyspaces().delete("shortestpath") - - def test_cluster_anwer_ConceptSet(self): - """ Test clustering with connected components response as ConceptSet """ - with client.session("clusterkeyspace") as local_session: - tx = local_session.transaction().write() - parentship_map = test_Answers._build_parentship(tx) # this closes the tx - tx = local_session.transaction().write() - result = tx.query("compute cluster in [person, parentship], using connected-component;") - concept_set_answer = next(result) - self.assertIsInstance(concept_set_answer, ConceptSet) - self.assertEqual(len(concept_set_answer.set()), 3) - self.assertTrue(parentship_map['parent'] in concept_set_answer.set()) - self.assertTrue(parentship_map['child'] in concept_set_answer.set()) - self.assertTrue(parentship_map['parentship'] in concept_set_answer.set()) - tx.close() - client.keyspaces().delete("clusterkeyspace") - - - def test_compute_centrality_answer_ConceptSetMeasure(self): - """ Test compute centrality, response type ConceptSetMeasure """ - with client.session("centralitykeyspace") as local_session: - tx = local_session.transaction().write() - parentship_map = test_Answers._build_parentship(tx) # this closes the tx - tx = local_session.transaction().write() - result = tx.query("compute centrality in [person, parentship], using degree;") - concept_set_measure_answer = next(result) - self.assertIsInstance(concept_set_measure_answer, ConceptSetMeasure) - self.assertEqual(concept_set_measure_answer.measurement(), 1) - self.assertTrue(parentship_map['parent'] in concept_set_measure_answer.set()) - self.assertTrue(parentship_map['child'] in concept_set_measure_answer.set()) - tx.close() - client.keyspaces().delete("centralitykeyspace") - - - def test_compute_aggregate_group_answer_AnswerGroup(self): - """ Test compute aggreate count, response type AnwerGroup """ - with client.session("aggregategroup") as local_session: - tx = local_session.transaction().write() - parentship_map = test_Answers._build_parentship(tx) # this closes the tx - tx = local_session.transaction().write() - result = tx.query("match $x isa person; $y isa person; (parent: $x, child: $y) isa parentship; get; group $x;") - answer_group = next(result) - self.assertIsInstance(answer_group, AnswerGroup) - self.assertEqual(answer_group.owner().id, parentship_map['parent']) - self.assertEqual(answer_group.answers()[0].get('x').id, parentship_map['parent']) - self.assertEqual(answer_group.answers()[0].map()['y'].id, parentship_map['child']) - tx.close() - client.keyspaces().delete("aggregategroup") - - - def test_delete_returns_Void(self): - """ Test `match...delete`, response type should be Void""" - with client.session("matchdelete_void") as local_session: - tx = local_session.transaction().write() - tx.query("define person sub entity;") - result = list(tx.query("insert $x isa person;")) - inserted_person = result[0].get("x") - person_id = inserted_person.id - - void_result = list(tx.query("match $x id {0}; delete $x isa thing;".format(person_id)))[0] - self.assertIsInstance(void_result, Void) - self.assertTrue("Deleted" in void_result.message()) - - self.assertTrue(inserted_person.as_remote(tx).is_deleted()) - tx.close() - client.keyspaces().delete("matchdelete_void") - - def test_compute_count_empty_graph_answer_Value(self): - with client.session("countingzero") as local_session: - tx = local_session.transaction().write() - tx.put_entity_type("foo") - result = tx.query("compute count in foo;") - answer = next(result) - self.assertIsInstance(answer, Value) - self.assertEqual(answer.number(), 0) - tx.close() - client.keyspaces().delete("countingzero") - - def test_aggr_count_empty_graph_answer_Value(self): - with client.session("countingnonzero") as local_session: - tx = local_session.transaction().write() - tx.query("define person sub entity; dog sub entity;") - result = tx.query("match $x sub entity; get $x; count;") - answer = next(result) - self.assertIsInstance(answer, Value) - self.assertEqual(answer.number(), 3) - tx.close() - client.keyspaces().delete("countingnonzero") - - @unittest.skip("behaviour changed on server side") - def test_conceptmap_explanation(self): - """ Test explanations when hitting a transitive rule """ - with client.session("transitivity") as local_session: - tx = local_session.transaction().write() - tx.query(""" - define - object sub entity, plays owned, plays owner; - ownership sub relation, relates owned, relates owner; - transitive-ownership sub rule, when { - (owned: $x, owner: $y) isa ownership; - (owned: $y, owner: $z) isa ownership; - }, then { - (owned: $x, owner: $z) isa ownership; - }; - """) - tx.query(""" - insert - $a isa object; $b isa object; $c isa object; $d isa object; $e isa object; - (owned: $a, owner: $b) isa ownership; - (owned: $b, owner: $c) isa ownership; - (owned: $c, owner: $d) isa ownership; - (owned: $d, owner: $e) isa ownership; - """) - tx.commit() - - tx = local_session.transaction().write() - answers = tx.query("match (owner: $x, owned: $y) isa ownership; get;") - - has_explanation = 0 - no_explanation = 0 - for concept_map in answers: - pattern = concept_map.query_pattern() - if concept_map.has_explanation(): - explanation = concept_map.explanation() - self.assertIsNotNone(explanation) - self.assertTrue(len(pattern) > 0) - for var in concept_map.map(): - self.assertTrue(("$" + var) in pattern) - has_explanation += 1 - else: - self.assertTrue(len(pattern) == 0) - no_explanation += 1 - - tx.close() - self.assertEqual(no_explanation, 4) - self.assertEqual(has_explanation, 6) - client.keyspaces().delete("transitivity") - - - def test_get_explanation_has_rule(self): - """ Test that explanations have rules attached """ - with client.session("explanation_has_rule") as local_session: - tx = local_session.transaction().write() - tx.query("define family-name sub attribute, value string;" - "parenthood sub relation, relates parent, relates child;" - "person sub entity, has family-name, plays parent, plays child;" - "family-name-inheritence sub rule," - "when { (parent: $p, child: $c) isa parenthood; $p has family-name $f; }," - "then { $c has family-name $f; };") - tx.query("insert $bob isa person, has family-name \"bobson\";" - "$bobjr isa person;" - "(parent: $bob, child: $bobjr) isa parenthood;") - - tx.commit() - tx = local_session.transaction().read() - - answers = tx.query("match $x isa person, has family-name $f; get;", explain=True) - for x in answers: - if x.has_explanation(): - explanation = x.explanation() - self.assertIsNotNone(explanation.get_rule()) - - client.keyspaces().delete("explanation_has_rule") - - - def test_query_with_explain_false_no_explanations_available(self): - with client.session("query_explain_false") as local_session: - tx = local_session.transaction().write() - tx.query("define family-name sub attribute, value string;" - "parenthood sub relation, relates parent, relates child;" - "person sub entity, has family-name, plays parent, plays child;" - "family-name-inheritence sub rule," - "when { (parent: $p, child: $c) isa parenthood; $p has family-name $f; }," - "then { $c has family-name $f; };") - tx.query("insert $bob isa person, has family-name \"bobson\";" - "$bobjr isa person;" - "(parent: $bob, child: $bobjr) isa parenthood;") - - tx.commit() - tx = local_session.transaction().read() - - answers = tx.query("match $x isa person, has family-name $f; get;", explain=False) - for x in answers: - self.assertFalse(x.has_explanation()) - - client.keyspaces().delete("query_explain_false") - - - def test_explain_true_explanation_sub_explanation_exists(self): - with client.session("query_explain_sub_explanation") as local_session: - tx = local_session.transaction().write() - tx.query("define family-name sub attribute, value string;" - "parenthood sub relation, relates parent, relates child;" - "fatherhood sub parenthood, relates father as parent, relates father-child as child;" - "person sub entity, has family-name, plays parent, plays child, plays father, plays father-child, has gender;" - "gender sub attribute, value string; " - "family-name-inheritence sub rule," - "when { (parent: $p, child: $c) isa parenthood; $p has family-name $f; }," - "then { $c has family-name $f; }; " - "fatherhood-rule sub rule, " - "when { $p isa person, has gender \"male\", has family-name $f; $c isa person, has family-name $f; }, " - "then { (father: $p, father-child: $c) isa fatherhood; };") - tx.query("insert $bob isa person, has family-name \"bobson\", has gender \"male\";" - "$bobjr isa person;" - "(parent: $bob, child: $bobjr) isa parenthood;") - - tx.commit() - tx = local_session.transaction().read() - - answers = tx.query("match $m isa fatherhood; get;", explain=True) - for x in answers: - if x.has_explanation(): - explanation = x.explanation() - self.assertIsNotNone(explanation.get_rule()) - sub_answers = explanation.get_answers() - self.assertTrue(sub_answers[0].has_explanation()) - self.assertIsNotNone(sub_answers[0].explanation()) - - client.keyspaces().delete("query_explain_sub_explanation") - -if __name__ == "__main__": - with GraknServer(): - unittest.main(verbosity=2) diff --git a/tests/integration/test_concept.py b/tests/integration/test_concept.py deleted file mode 100644 index 24ba72eb..00000000 --- a/tests/integration/test_concept.py +++ /dev/null @@ -1,786 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -import unittest -from grakn.client import GraknClient, ValueType -import datetime -import uuid -from grakn.exception.GraknError import GraknError - - -from tests.integration.base import test_Base, GraknServer - -client = None -session = None - -class test_concept_Base(test_Base): - """ Sets up DB for use in tests """ - - @classmethod - def setUpClass(cls): - """ Make sure we have some sort of schema and data in DB, only done once """ - super(test_concept_Base, cls).setUpClass() - - global client, session - - # TODO this is not neat - this is basically emulating a constructor/destructor operation using globals - - client = GraknClient("localhost:48555") - keyspace = "test_" + str(uuid.uuid4()).replace("-", "_")[:8] - session = client.session(keyspace) - # temp tx to set up DB, don"t save it - tx = session.transaction().write() - try: - # define parentship roles to test agains - tx.query("define " - "parent sub role; " - "child sub role; " - "mother sub role; " - "son sub role; " - "person sub entity, has age, has gender, plays parent, plays child, plays mother, plays son; " - "age sub attribute, value long; " - "gender sub attribute, value string; " - "parentship sub relation, relates parent, relates child, relates mother, relates son;") - except GraknError as ce: - print(ce) - - answers = list(tx.query("match $x isa person, has age 20; get;")) - if len(answers) == 0: - tx.query("insert $x isa person, has age 20;") - tx.commit() - - @classmethod - def tearDownClass(cls): - super(test_concept_Base, cls).tearDownClass() - global session, client - session.close() - # clear the test keyspace - client.keyspaces().delete(session.keyspace) - client.close() - - def setUp(self): - global session - self.tx = session.transaction().write() - # functions called by `addCleanup` are reliably called independent of test pass or failure - self.addCleanup(self.cleanupTransaction, self.tx) - - def cleanupTransaction(self, tx): - tx.close() - - -class test_Concept(test_concept_Base): - """ Test methods available on all Concepts """ - - def test_delete_schema_types(self): - car_type = self.tx.put_entity_type("car") - schema_concept = self.tx.get_schema_concept("car") - self.assertTrue(schema_concept.is_schema_concept()) - schema_concept.delete() - none_schema_car = self.tx.get_schema_concept("car") - self.assertIsNone(none_schema_car, msg="Deletion of car schema type failed") - - def test_delete_instance(self): - car_type = self.tx.put_entity_type("car") - car = car_type.create() - - car.delete() - none_car = self.tx.get_concept(car.id) - self.assertIsNone(none_car, msg="Deletion of car instance failed") - - def test_re_delete_instance(self): - car_type = self.tx.put_entity_type("car") - car = car_type.create() - - car.delete() - none_car = self.tx.get_concept(car.id) - self.assertIsNone(none_car) - - with self.assertRaises(GraknError) as context: - car.delete() - - self.assertTrue("FAILED_PRECONDITION" in str(context.exception)) - - def test_is_deleted(self): - car_type = self.tx.put_entity_type("car") - car = car_type.create() - self.assertFalse(car.is_deleted()) - - car.delete() - self.assertTrue(car.is_deleted()) - - car2 = car_type.create() - self.tx.query("match $x isa car; delete $x isa car;") - self.assertTrue(car2.is_deleted) - - - def test_is_each_schema_type(self): - car_type = self.tx.put_entity_type("car") - car = car_type.create() - self.assertTrue(car.is_entity()) - self.assertFalse(car.is_attribute()) - self.assertFalse(car.is_relation()) - - rel_type = self.tx.put_relation_type("owner") - owner = rel_type.create() - self.assertFalse(owner.is_entity()) - self.assertFalse(owner.is_attribute()) - self.assertTrue(owner.is_relation()) - - attr_type = self.tx.put_attribute_type("age", ValueType.LONG) - age = attr_type.create(50) - self.assertFalse(age.is_entity()) - self.assertTrue(age.is_attribute()) - self.assertFalse(age.is_relation()) - -class test_SchemaConcept(test_concept_Base): - """ Test methods available on all SchemaConcepts """ - - def test_set_label(self): - """ Get and set labels """ - with self.subTest(i=0): - # get label - car_schema_type = self.tx.put_entity_type("car") - car_type = self.tx.get_schema_concept("car") - self.assertEqual(car_type.label(), "car") - - with self.subTest(i=1): - # set label - car_type = self.tx.get_schema_concept("car") - car_type.label("vehicle") - vehicle_type = self.tx.get_schema_concept("vehicle") - self.assertEqual(vehicle_type.label(), "vehicle") - - with self.subTest(i=2): - bike_type = self.tx.get_schema_concept("bike") - with self.assertRaises(AttributeError): - bike_type.label("") - with self.assertRaises(AttributeError): - bike_type.label(100) - self.assertIsNone(bike_type) - - - def test_get_sups(self): - """ Test get super types of a schema concept -- recall a type is supertype of itself always """ - person = self.tx.get_schema_concept("person") - sups = list(person.sups()) - self.assertEqual(len(sups), 2, msg="person does not have 2 sups") - sup_labels = [concept.label() for concept in sups] - self.assertTrue("person" in sup_labels and "entity" in sup_labels) - - # check supertype of toplevel schema concepts - schema_entity = self.tx.get_schema_concept("entity") - thing_type = schema_entity.sup() # this is Thing - self.assertEqual(thing_type.base_type, "META_TYPE") - thing_sup = thing_type.sup() - self.assertIsNone(thing_sup) - - def test_set_sups(self): - """ Test setting super type of a schema concept """ - human_schema_concept = self.tx.put_entity_type("human") - male_schema_concept = self.tx.put_entity_type("male") - human_sup = human_schema_concept.sup() - self.assertEqual(human_sup.base_type, "ENTITY_TYPE") - - male_schema_concept.sup(human_schema_concept) - sup = male_schema_concept.sup() - self.assertEqual(sup.label(), "human") - - def test_get_subs(self): - """ Test get sub types of schema concept -- recall a type is a subtype of itself always """ - entity = self.tx.get_schema_concept("entity") - subs = list(entity.subs()) - self.assertEqual(len(subs), 2, msg="entity does not have 2 subs") - subs_labels = [sub.label() for sub in subs] - self.assertTrue('entity' in subs_labels and 'person' in subs_labels) - - - - -class test_Type(test_concept_Base): - """ Tests concept API of things common to Type objects """ - - def test_is_abstract(self): - """ Tests get/set of is_abstract on types """ - dog_type = self.tx.put_entity_type("dog") - with self.subTest(i=0): - abstract = dog_type.is_abstract() - self.assertFalse(abstract) - with self.subTest(i=1): - dog_type.is_abstract(True) - abstract = dog_type.is_abstract() #re-retrieve from server - self.assertTrue(abstract) - with self.subTest(i=2): - dog_type.is_abstract(False) - abstract = dog_type.is_abstract() - self.assertFalse(abstract) - - def test_plays_methods(self): - """ Test get/set/delete plays ie. roles """ - father = self.tx.put_role("father") - with self.subTest(i=0): - person_schema_type = self.tx.get_schema_concept("person") - person_plays = list(person_schema_type.playing()) - self.assertEqual(len(person_plays), 4) - with self.subTest(i=1): - person_schema_type.plays(father) - updated_person_plays = person_schema_type.playing() - labels = [role.label() for role in updated_person_plays] - self.assertEqual(len(labels), 5) - self.assertTrue("father" in labels) - with self.subTest(i=2): - # remove role/plays from person - person_schema_type.unplay(father) - updated_person_plays = person_schema_type.playing() - labels = [role.label() for role in updated_person_plays] - self.assertEqual(len(labels), 4) - self.assertFalse("father" in labels) - - def test_attributes_methods(self): - """ Test get/set/delete attributes """ - person = self.tx.get_schema_concept("person") - haircolor_attr = self.tx.put_attribute_type("haircolor", ValueType.STRING) - with self.subTest(i=0): - # get attrs - current_attrs = person.attributes() - labels = [attr.label() for attr in current_attrs] - self.assertEqual(len(labels), 2) # has age, gender to start with - with self.subTest(i=1): - # add an attr - person.has(haircolor_attr) - new_attrs = person.attributes() - new_labels = [attr.label() for attr in new_attrs] - self.assertEqual(len(new_labels), 3) - self.assertTrue('haircolor' in new_labels) - with self.subTest(i=2): - # delete an attrs - person.unhas(haircolor_attr) - attrs_fewer = person.attributes() - labels_fewer = [attr.label() for attr in attrs_fewer] - self.assertEqual(len(labels_fewer), 2) - self.assertFalse('haircolor' in labels_fewer) - - def test_instances(self): - """ Test retrieving instances of a type """ - person = self.tx.get_schema_concept("person") - people = list(person.instances()) - person_inst = person.create() - people_more = list(person.instances()) - self.assertEqual(len(people_more) - len(people), 1) - - def test_key(self): - """ Test get/set/delete key on Type """ - person_type = self.tx.get_schema_concept("person") - name_attr_type = self.tx.put_attribute_type('name', ValueType.STRING) - - with self.subTest(i=0): - # check current keys - keys = list(person_type.keys()) - self.assertEqual(len(keys), 0, "Person has more than 0 keys already") - with self.subTest(i=1): - # set a key - person_type.key(name_attr_type) - keys = list(person_type.keys()) - self.assertEqual(len(keys), 1) - self.assertEqual(keys[0].base_type, "ATTRIBUTE_TYPE") - self.assertEqual(keys[0].label(), 'name') - with self.subTest(i=2): - # remove a key - person_type.unkey(name_attr_type) - keys = list(person_type.keys()) - self.assertEqual(len(keys), 0) - - -class test_EntityType(test_concept_Base): - - def test_create(self): - person_type = self.tx.get_schema_concept("person") - person = person_type.create() - self.assertTrue(person.is_entity()) - - -class test_AttributeType(test_concept_Base): - - def test_create(self): - str_attr_type = self.tx.put_attribute_type("firstname", ValueType.STRING) - john = str_attr_type.create("john") - self.assertTrue(john.is_attribute()) - self.assertEqual(john.value(), "john") - - bool_attr_type = self.tx.put_attribute_type("employed", ValueType.BOOLEAN) - employed = bool_attr_type.create(True) - self.assertEqual(employed.value(), True) - - double_attr_type = self.tx.put_attribute_type("length", ValueType.DOUBLE) - one = double_attr_type.create(1.0) - self.assertEqual(one.value(), 1.0) - - def test_value_type(self): - str_attr_type = self.tx.put_attribute_type("firstname", ValueType.STRING) - self.assertEqual(str_attr_type.value_type(), ValueType.STRING) - - bool_attr_type = self.tx.put_attribute_type("employed", ValueType.BOOLEAN) - self.assertEqual(bool_attr_type.value_type(), ValueType.BOOLEAN) - - double_attr_type = self.tx.put_attribute_type("length", ValueType.DOUBLE) - self.assertEqual(double_attr_type.value_type(), ValueType.DOUBLE) - - long_attr_type = self.tx.put_attribute_type("randomint", ValueType.LONG) - self.assertEqual(long_attr_type.value_type(), ValueType.LONG) - - def test_attribute(self): - """ Test retrieve attribute instances """ - - name = self.tx.put_attribute_type("name", ValueType.STRING) - john = name.create("john") - - with self.subTest(i=0): - # retrieve existing attr client - retrieved_john = name.attribute("john") - self.assertEqual(retrieved_john.value(), john.value()) - self.assertTrue(retrieved_john.is_attribute()) - with self.subTest(i=1): - # retrieve nonexistant attr client - retrieved_none = name.attribute("nobody") - self.assertIsNone(retrieved_none) - - def test_regex(self): - """ Test get/set regex """ - attr_type = self.tx.put_attribute_type("dogbadness", ValueType.STRING) - - empty_regex = attr_type.regex() - self.assertEqual(len(empty_regex), 0, msg="Unset regex does not have length 0") - - attr_type.regex("(good|bad)-dog") - regex = attr_type.regex() - self.assertEqual(regex, "(good|bad)-dog") - - -class test_RelationType(test_concept_Base): - - def test_create(self): - rel_type = self.tx.put_relation_type("owner") - rel = rel_type.create() - self.assertTrue(rel.is_relation()) - self.assertTrue(rel_type.is_relation_type()) - - def test_relates(self): - """ Test get/relate/unrelate roles for a relation type """ - ownership = self.tx.put_relation_type("ownership") - role_owner = self.tx.put_role("owner") - role_owned = self.tx.put_role("owned") - - with self.subTest(i=0): - # currently no roles in the new relation - roles = list(ownership.roles()) - self.assertEqual(len(roles), 0) - with self.subTest(i=1): - # set roles in relation - ownership.relates(role_owner) - ownership.relates(role_owned) - roles = list(ownership.roles()) - self.assertEqual(len(roles), 2) - with self.subTest(i=2): - # unrelate a role - ownership.unrelate(role_owned) - roles = list(ownership.roles()) - self.assertEqual(len(roles), 1) - self.assertEqual(roles[0].base_type, "ROLE") - - -class test_Rule(test_concept_Base): - - def test_when_then(self): - """ Test get valid when/then """ - label = "genderizedparentship" - when = "{ (parent: $p, child: $c) isa parentship; $c has gender \"male\"; $p has gender \"female\"; };" - then = "{ (mother: $p, son: $c) isa parentship; };" - rule = self.tx.put_rule(label, when, then) - - self.assertEqual(rule.get_when(), when) - self.assertEqual(rule.get_then(), then) - - def test_none_when_then(self): - """ Test get when/then for rule with null when/then """ - rule = self.tx.get_schema_concept('rule') - self.assertIsNone(rule.get_when()) - self.assertIsNone(rule.get_then()) - - -class test_Role(test_concept_Base): - - def test_relations(self): - """ Test retrieving relations of a role """ - # parent role, parentship already exist - result = [ans.get("x") for ans in self.tx.query("match $x type parent; get;")] - parent_role = result[0].as_remote(self.tx) - self.assertEqual(parent_role.base_type, "ROLE") - - relations = list(parent_role.relations()) - self.assertEqual(len(relations), 1) - self.assertEqual(relations[0].base_type, "RELATION_TYPE") - self.assertEqual(relations[0].label(), "parentship") - - def test_players(self): - """ Test retrieving entity types playing this role """ - result = [ans.get("x") for ans in self.tx.query("match $x type parent; get;")] - parent_role = result[0].as_remote(self.tx) - self.assertEqual(parent_role.base_type, "ROLE") - - entity_types = list(parent_role.players()) - self.assertEqual(len(entity_types), 1) - self.assertEqual(entity_types[0].base_type, "ENTITY_TYPE") - self.assertEqual(entity_types[0].label(), "person") - - -class test_Thing(test_concept_Base): - - def test_is_inferred(self): - person_type = self.tx.get_schema_concept("person") - person = person_type.create() - self.assertFalse(person.is_inferred()) - - def test_type(self): - person_type = self.tx.get_schema_concept("person") - person = person_type.create() - p_type = person.type() - self.assertEqual(p_type.id, person_type.id) # same schema concept - self.assertTrue(p_type.is_type()) - - def test_relations(self): - """ Test retrieve relations narrowed optionally by roles """ - # create a first relation - sibling_type = self.tx.put_relation_type('sibling') - brother_role = self.tx.put_role("brother") - sibling_type.relates(brother_role) - person = self.tx.get_schema_concept("person") - - # create a second relation - ownership_type = self.tx.put_relation_type("ownership") - owner_role = self.tx.put_role("owner") - ownership_type.relates(owner_role) - person.plays(owner_role) - - # connect entities/relation instances - sibling = sibling_type.create() - ownership = ownership_type.create() - son = person.create() - sibling.assign(brother_role, son) # assign son to sibling rel - ownership.assign(owner_role, son) # attach son to owner rel - - # retrieve all relations - rels = list(son.relations()) - self.assertEqual(len(rels), 2) - rel_ids = [rel.id for rel in rels] - self.assertTrue(sibling.id in rel_ids and ownership.id in rel_ids) - - - # retrieve filtered by only the owner role - filtered_rels = list(son.relations(owner_role)) - self.assertEqual(len(filtered_rels), 1) - self.assertEqual(filtered_rels[0].id, ownership.id) - - def test_roles(self): - # create a relation - ownership_type = self.tx.put_relation_type("ownership") - owner_role = self.tx.put_role("owner") - ownership_type.relates(owner_role) - person_type = self.tx.get_schema_concept("person") - person_type.plays(owner_role) - - # connect entities/relation instances - ownership = ownership_type.create() - person = person_type.create() - ownership.assign(owner_role, person) # attach son to owner rel - - roles = list(person.roles()) - self.assertEqual(len(roles), 1) - self.assertEqual(roles[0].id, owner_role.id) - - - def test_has_unhas_attributes(self): - """ Test has/unhas/get attributes """ - person_type = self.tx.get_schema_concept("person") - name_attr_type = self.tx.put_attribute_type("name", ValueType.STRING) - person_type.has(name_attr_type) - person = person_type.create() - attr_john = name_attr_type.create("john") - person.has(attr_john) - - attrs = list(person.attributes()) - self.assertEqual(len(attrs), 1) - self.assertEqual(attrs[0].id, attr_john.id) - - person.unhas(attr_john) - empty_attrs = list(person.attributes()) - self.assertEqual(len(empty_attrs), 0) - - def test_attributes(self): - """ Test retrieve attrs optionally narrowed by types """ - person_type = self.tx.get_schema_concept("person") - name_attr = self.tx.put_attribute_type("name", ValueType.STRING) - foo_attr = self.tx.put_attribute_type("foo", ValueType.BOOLEAN) - bar_attr = self.tx.put_attribute_type("bar", ValueType.LONG) - - person_type.has(name_attr) - person_type.has(foo_attr) - - person = person_type.create() - name = name_attr.create("john") - foo = foo_attr.create(False) - person.has(name) - person.has(foo) - - attrs = list(person.attributes()) - self.assertEqual(len(attrs), 2) - for attr in attrs: - self.assertTrue(attr.is_attribute()) - - #filtered attrs - attrs = list(person.attributes(name_attr)) - self.assertEqual(len(attrs), 1) - self.assertTrue(attrs[0].is_attribute()) - self.assertEqual(attrs[0].id, name.id) - attrs = list(person.attributes(name_attr, foo_attr)) - self.assertEqual(len(attrs), 2) - - #nonexistant filtering - attrs = list(person.attributes(bar_attr)) # not attached - self.assertEqual(len(attrs), 0) - - def test_keys(self): - """ Test retrieving keys optionally filtered by attribute types """ - person_type = self.tx.get_schema_concept("person") - name_type = self.tx.put_attribute_type("name", ValueType.STRING) - surname_type = self.tx.put_attribute_type("surname", ValueType.STRING) - person_type.key(name_type) - person_type.has(surname_type) - - name = name_type.create("john") - surname = surname_type.create("lennon") - person = person_type.create() - person.has(name) - person.has(surname) - - keys = list(person.keys()) - self.assertEqual(len(keys), 1) - self.assertEqual(keys[0].id, name.id) - - filtered_keys = list(person.keys(name_type, surname_type)) - self.assertEqual(len(filtered_keys), 1) - self.assertEqual(filtered_keys[0].id, name.id) - - empty_keys = list(person.keys(surname_type)) - self.assertEqual(len(empty_keys), 0) - - -class test_Attribute(test_concept_Base): - - def test_value(self): - """ Get attribute value """ - double_attr_type = self.tx.put_attribute_type("length", ValueType.DOUBLE) - double = double_attr_type.create(43.1) - self.assertEqual(double.value(), 43.1) - - def test_get_date_value(self): - date_type = self.tx.put_attribute_type("birthdate", ValueType.DATETIME) - person_type = self.tx.get_schema_concept("person") - person_type.has(date_type) - concepts = [ans.get("x") for ans in self.tx.query("insert $x isa person, has birthdate 2018-08-06;")] - person = concepts[0].as_remote(self.tx) - attrs_iter = person.attributes() - for attr_concept in attrs_iter: - # pick out the birthdate - if attr_concept.type().label() == "birthdate": - date = attr_concept.value() - self.assertIsInstance(date, datetime.datetime) - self.assertEqual(date.year, 2018) - self.assertEqual(date.month, 8) - self.assertEqual(date.day, 6) - return - - def test_set_date_value(self): - date_type = self.tx.put_attribute_type("birthdate", ValueType.DATETIME) - test_date = datetime.datetime(year=2018, month=6, day=6) - date_attr_inst = date_type.create(test_date) - value = date_attr_inst.value() # retrieve from server - self.assertIsInstance(value, datetime.datetime) - self.assertEqual(value.timestamp(), test_date.timestamp()) - - def test_owners(self): - """ Test retrieving entities that have an attribute """ - person_type = self.tx.get_schema_concept("person") - animal_type = self.tx.put_entity_type("animal") - name_type = self.tx.put_attribute_type("name", ValueType.STRING) - person_type.has(name_type) - animal_type.has(name_type) - - person = person_type.create() - animal = animal_type.create() - john = name_type.create("john") - - person.has(john) - animal.has(john) - - owners = list(john.owners()) - self.assertEqual(len(owners), 2) - labels = [x.id for x in owners] - self.assertTrue(person.id in labels and animal.id in labels) - - -class test_Relation(test_concept_Base): - - def test_role_players_2_roles_1_player(self): - """ Test role_players_map and role_players with 2 roles and 1 player each """ - parentship_type = self.tx.get_schema_concept("parentship") - person_type = self.tx.get_schema_concept("person") - parent_role = self.tx.get_schema_concept("parent") - child_role = self.tx.get_schema_concept("child") - - parent = person_type.create() - child = person_type.create() - parentship = parentship_type.create() - - parentship.assign(parent_role, parent) - parentship.assign(child_role, child) - - role_players_map = parentship.role_players_map() - self.assertEqual(len(role_players_map.keys()), 2) - for role in role_players_map: - players_list = role_players_map[role] - self.assertEqual(len(players_list), 1) - self.assertTrue(role.is_role()) - - role_players = list(parentship.role_players()) - self.assertEqual(len(role_players), 2) - - def test_role_players_1_role_2_players(self): - parentship_type = self.tx.get_schema_concept("parentship") - person_type = self.tx.get_schema_concept("person") - parent_role = self.tx.get_schema_concept("parent") - - parent = person_type.create() - another_parent = person_type.create() - parentship = parentship_type.create() - - parentship.assign(parent_role, parent) - parentship.assign(parent_role, another_parent) - - role_players_map = parentship.role_players_map() - self.assertEqual(len(role_players_map.keys()), 1) - for role in role_players_map: - players_list = role_players_map[role] - self.assertEqual(len(players_list), 2) - self.assertTrue(role.is_role()) - - role_players = list(parentship.role_players()) - self.assertEqual(len(role_players), 2) - - def test_role_players_2_roles_same_player(self): - parentship_type = self.tx.get_schema_concept("parentship") - person_type = self.tx.get_schema_concept("person") - parent_role = self.tx.get_schema_concept("parent") - child_role = self.tx.get_schema_concept("child") - - self_parent = person_type.create() - parentship = parentship_type.create() - - parentship.assign(parent_role, self_parent) - parentship.assign(child_role, self_parent) - - role_players_map = parentship.role_players_map() - self.assertEqual(len(role_players_map.keys()), 2) - for role in role_players_map: - players_list = role_players_map[role] - self.assertEqual(len(players_list), 1) - self.assertTrue(role.is_role()) - - role_players = list(parentship.role_players()) - self.assertEqual(len(role_players), 2) - self.assertTrue(role_players[0].is_thing()) - - def test_role_players_1_role_same_player(self): - parentship_type = self.tx.get_schema_concept("parentship") - person_type = self.tx.get_schema_concept("person") - parent_role = self.tx.get_schema_concept("parent") - - self_parent = person_type.create() - parentship = parentship_type.create() - - parentship.assign(parent_role, self_parent) - parentship.assign(parent_role, self_parent) - - role_players_map = parentship.role_players_map() - self.assertEqual(len(role_players_map.keys()), 1) - for role in role_players_map: - players_list = role_players_map[role] - self.assertEqual(len(players_list), 2) - self.assertTrue(role.is_role()) - - role_players = list(parentship.role_players()) - self.assertEqual(len(role_players), 2) - self.assertTrue(role_players[0].is_thing()) - - def test_assign_unassign(self): - parentship_type = self.tx.get_schema_concept("parentship") - person_type = self.tx.get_schema_concept("person") - parent_role = self.tx.get_schema_concept("parent") - - person = person_type.create() - parentship = parentship_type.create() - - empty_role_players = list(parentship.role_players()) - self.assertEqual(len(empty_role_players), 0) - - parentship.assign(parent_role, person) - role_players = list(parentship.role_players()) - self.assertEqual(len(role_players), 1) - self.assertEqual(role_players[0].id, person.id) - - parentship.unassign(parent_role, person) - self.assertTrue(parentship.is_deleted()) - - - def test_role_players_filtered_by_role(self): - parentship_type = self.tx.get_schema_concept("parentship") - person_type = self.tx.get_schema_concept("person") - parent_role = self.tx.get_schema_concept("parent") - child_role = self.tx.get_schema_concept("child") - - parent = person_type.create() - child = person_type.create() - parentship = parentship_type.create() - parentship.assign(parent_role, parent) - parentship.assign(child_role, child) - - # no filter - role_players = list(parentship.role_players()) - self.assertEqual(len(role_players), 2) - # single filter - filtered_role_players = list(parentship.role_players(child_role)) - self.assertEqual(len(filtered_role_players), 1) - self.assertEqual(filtered_role_players[0].id, child.id) - - # allow both - double_filter_role_players = list(parentship.role_players(child_role, parent_role)) - self.assertEqual(len(double_filter_role_players), 2) - - -if __name__ == "__main__": - with GraknServer(): - unittest.main(verbosity=2) diff --git a/tests/integration/test_keyspace.py b/tests/integration/test_keyspace.py deleted file mode 100644 index ff808734..00000000 --- a/tests/integration/test_keyspace.py +++ /dev/null @@ -1,59 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -import unittest -from grakn.client import GraknClient -from tests.integration.base import test_Base, GraknServer - -client = None -session = None - -class test_Keyspace(test_Base): - @classmethod - def setUpClass(cls): - super(test_Keyspace, cls).setUpClass() - global client, session - client = GraknClient("localhost:48555") - session = client.session("keyspacetest") - - @classmethod - def tearDownClass(cls): - super(test_Keyspace, cls).tearDownClass() - global client, session - session.close() - client.close() - - def test_retrieve_delete(self): - """ Test retrieving and deleting a specific keyspace """ - - tx = session.transaction().write() - tx.close() - - keyspaces = client.keyspaces().retrieve() - self.assertGreater(len(keyspaces), 0) - self.assertTrue('keyspacetest' in keyspaces) - - client.keyspaces().delete('keyspacetest') - post_delete_keyspaces = client.keyspaces().retrieve() - self.assertFalse('keyspacetest' in post_delete_keyspaces) - - -if __name__ == "__main__": - with GraknServer(): - unittest.main(verbosity=2)