├── .github └── workflows │ ├── constraints.txt │ ├── docs.yml │ ├── release.yml │ └── test.yml ├── .gitignore ├── LICENSE ├── README.md ├── README.rst ├── docs ├── assets │ ├── images │ │ ├── arch.png │ │ ├── geo-logo-white.svg │ │ ├── layout.png │ │ ├── new_shp_vector.png │ │ └── workspace_created.png │ ├── javascripts │ │ ├── custom.js │ │ ├── old_custom.js │ │ ├── old_termynal.js │ │ └── termynal.js │ └── stylesheets │ │ ├── custom.css │ │ ├── old_custom.css │ │ ├── old_termynal.css │ │ └── termynal.css ├── index.md └── pages │ ├── async │ ├── example.md │ ├── index.md │ ├── raster-store.md │ ├── style.md │ ├── vector-store.md │ └── workspace.md │ ├── cli │ ├── example.md │ ├── index.md │ ├── raster-store.md │ ├── style.md │ ├── vector-store.md │ └── workspace.md │ └── sync │ ├── example.md │ ├── index.md │ ├── raster-store.md │ ├── style.md │ ├── vector-store.md │ └── workspace.md ├── mkdocs.yml ├── poetry.lock ├── pyproject.toml ├── social ├── 2be55c97396bb6811061ba3194e30e4d.png ├── 53d44762e0d6016d6de24215dfdd6676.png ├── 548c1d117ad531ea470f4c6f675062a4.png ├── 92f5fd10f5766422436223bc15a7c8ef.png ├── 9eef2d77b3d71b7533499b2f651f63d4.png ├── a05d671ab8d4f043eadc8e9f00b2feec.png ├── ab5486e06e38b9b0518f6930cc6a8248.png ├── df41e99874754a66cab3b04028b0eb6b.png ├── f510d45f3b407da60e907251443fcf59.png └── fonts │ └── Roboto │ ├── Black Italic.ttf │ ├── Black.ttf │ ├── Bold Italic.ttf │ ├── Bold.ttf │ ├── Italic.ttf │ ├── Light Italic.ttf │ ├── Light.ttf │ ├── Medium Italic.ttf │ ├── Medium.ttf │ ├── Regular.ttf │ ├── Thin Italic.ttf │ └── Thin.ttf ├── src └── geoserverx │ ├── __init__.py │ ├── _async │ ├── __init__.py │ └── gsx.py │ ├── _sync │ ├── __init__.py │ └── gsx.py │ ├── cli │ └── cli.py │ ├── models │ ├── __init__.py │ ├── coverages_layer.py │ ├── coverages_store.py │ ├── data_store.py │ ├── featuretypes_layer.py │ ├── geofence.py │ ├── gs_response.py │ ├── layer_group.py │ ├── layers.py │ ├── style.py │ └── workspace.py │ └── utils │ ├── __init__.py │ ├── auth.py │ ├── custom_exceptions.py │ ├── enums.py │ ├── errors.py │ ├── http_client.py │ ├── logger.py │ └── services │ ├── async_datastore.py │ └── datastore.py └── tests ├── _async ├── __init__.py └── test_gsx.py ├── _sync ├── __init__.py └── test_gsx.py ├── cli └── test_cli.py ├── conftest.py └── test_models.py /.github/workflows/constraints.txt: -------------------------------------------------------------------------------- 1 | pip==24.2 2 | poetry==1.8.3 3 | virtualenv==20.26.3 4 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: docs 2 | on: 3 | push: 4 | branches: ["document/**"] 5 | pull_request: 6 | branches: [ "main" ] 7 | permissions: 8 | contents: write 9 | jobs: 10 | deploy: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v3 14 | - uses: actions/setup-python@v4 15 | with: 16 | python-version: 3.x 17 | - name: Install Poetry 18 | uses: snok/install-poetry@v1 19 | with: 20 | version: 1.5.1 21 | - name: Install dependencies 22 | run: poetry install --with docs 23 | - run: echo "cache_id=$(date --utc '+%V')" >> $GITHUB_ENV 24 | - uses: actions/cache@v3 25 | with: 26 | key: mkdocs-material-${{ env.cache_id }} 27 | path: .cache 28 | restore-keys: | 29 | mkdocs-material- 30 | - name: Deploy Documentation 31 | run: | 32 | poetry run mkdocs gh-deploy --force 33 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - master 8 | 9 | jobs: 10 | release: 11 | name: Release 12 | runs-on: ubuntu-latest 13 | steps: 14 | 15 | - name: Check out the repository 16 | uses: actions/checkout@v2.4.0 17 | with: 18 | fetch-depth: 2 19 | 20 | - name: Set up Python 21 | uses: actions/setup-python@v2.3.0 22 | with: 23 | python-version: "3.10" 24 | 25 | - name: Upgrade pip 26 | run: | 27 | pip install --upgrade pip 28 | pip --version 29 | - name: Install Poetry 30 | uses: snok/install-poetry@v1 31 | with: 32 | version: 1.5.1 33 | - name: Check if there is a parent commit 34 | id: check-parent-commit 35 | run: | 36 | echo "::set-output name=sha::$(git rev-parse --verify --quiet HEAD^)" 37 | 38 | - name: Build and publish to pypi 39 | uses: JRubics/poetry-publish@v1.16 40 | with: 41 | pypi_token: ${{ secrets.PYPI_TOKEN }} 42 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies, run tests and lint with a single version of Python 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python 3 | 4 | name: Test 5 | 6 | on: 7 | push: 8 | branches: [ "develop" , "feature/**", "hotfix/**"] 9 | pull_request: 10 | branches: [ "main" , "develop"] 11 | 12 | permissions: 13 | contents: read 14 | 15 | jobs: 16 | test: 17 | name : ${{ matrix.python }} Test 18 | runs-on: ${{ matrix.os }} 19 | strategy: 20 | fail-fast: false 21 | matrix: 22 | include: 23 | - { python: "3.10", os: "ubuntu-20.04" } 24 | - { python: "3.9", os: "ubuntu-20.04" } 25 | 26 | steps: 27 | - name: Check out the repository 28 | uses: actions/checkout@v4 29 | 30 | - name: Set up Python ${{ matrix.python }} 31 | uses: actions/setup-python@v5 32 | with: 33 | python-version: ${{ matrix.python }} 34 | 35 | - name: Upgrade pip 36 | run: | 37 | pip install pip 38 | pip --version 39 | - name: Upgrade pip in virtual environments 40 | shell: python 41 | run: | 42 | import os 43 | import pip 44 | with open(os.environ["GITHUB_ENV"], mode="a") as io: 45 | print(f"VIRTUALENV_PIP={pip.__version__}", file=io) 46 | - name: Install Poetry 47 | uses: snok/install-poetry@v1 48 | with: 49 | version: 1.5.1 50 | - name: Install dependencies 51 | run: poetry install 52 | - name: Run tests 53 | run: poetry run pytest 54 | 55 | - name: Run black 56 | uses: psf/black@stable 57 | 58 | - name: Run isort 59 | uses: isort/isort-action@v1 60 | with : 61 | configuration : "--profile black" 62 | 63 | - name : Ruff Check 64 | uses: jpetrucciani/ruff-check@main 65 | with: 66 | flags: '--fix' -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | **/__pycache__/ 4 | *.py[cod] 5 | *$py.class 6 | 7 | # C extensions 8 | *.so 9 | 10 | # Distribution / packaging 11 | .Python 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | pip-wheel-metadata/ 25 | share/python-wheels/ 26 | *.egg-info/ 27 | .installed.cfg 28 | *.egg 29 | MANIFEST 30 | 31 | # PyInstaller 32 | # Usually these files are written by a python script from a template 33 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 34 | *.manifest 35 | *.spec 36 | 37 | # Installer logs 38 | pip-log.txt 39 | pip-delete-this-directory.txt 40 | 41 | # Unit test / coverage reports 42 | htmlcov/ 43 | .tox/ 44 | .nox/ 45 | .coverage 46 | .coverage.* 47 | .cache 48 | nosetests.xml 49 | coverage.xml 50 | *.cover 51 | *.py,cover 52 | .hypothesis/ 53 | .pytest_cache/ 54 | 55 | # Translations 56 | *.mo 57 | *.pot 58 | 59 | # Django stuff: 60 | *.log 61 | local_settings.py 62 | db.sqlite3 63 | db.sqlite3-journal 64 | 65 | # Flask stuff: 66 | instance/ 67 | .webassets-cache 68 | 69 | # Scrapy stuff: 70 | .scrapy 71 | 72 | # Sphinx documentation 73 | docs/_build/ 74 | 75 | # PyBuilder 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | .python-version 87 | 88 | # pipenv 89 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 90 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 91 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 92 | # install all needed dependencies. 93 | #Pipfile.lock 94 | 95 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 96 | __pypackages__/ 97 | 98 | # Celery stuff 99 | celerybeat-schedule 100 | celerybeat.pid 101 | 102 | # SageMath parsed files 103 | *.sage.py 104 | 105 | # Environments 106 | .env 107 | .venv 108 | env/ 109 | venv/ 110 | ENV/ 111 | env.bak/ 112 | venv.bak/ 113 | 114 | # Spyder project settings 115 | .spyderproject 116 | .spyproject 117 | 118 | # Rope project settings 119 | .ropeproject 120 | 121 | # mkdocs documentation 122 | /site 123 | 124 | # mypy 125 | .mypy_cache/ 126 | .dmypy.json 127 | dmypy.json 128 | 129 | # Pyre type checker 130 | .pyre/ 131 | 132 | 133 | data_check/* -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Geobeyond Srl 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # geoserverx 2 | A GeoServer REST API client influenced by HTTPX 3 | 4 | ----------- 5 | 6 | `geoserverx` allows `Sync`, `Async` as well as `CLI` Capabilities to talk to your GeoServer REST APIs which makes it ideal to be used in software development project which relies on content of GeoServer. 7 | 8 | Here is a simplistic view of how geoserverx works under the hood 9 | ![architecture](/docs/assets/images/arch.png "architecture") 10 | 11 | 12 | 13 | 14 | 15 | ## Contribution guide 16 | 17 | ### Feature/Bug request 18 | Please open new issue if you want to report any bug or send feature request 19 | 20 | ### Running on local 21 | `geoserverx` is built with [poetry](https://python-poetry.org/) and it uses following dependencies and dev-dependencies 22 | 23 | ![layout](/docs/assets/images/layout.png "layout") -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geobeyond/geoserverx/64349483b643d4c662da9d358286728360a38015/README.rst -------------------------------------------------------------------------------- /docs/assets/images/arch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geobeyond/geoserverx/64349483b643d4c662da9d358286728360a38015/docs/assets/images/arch.png -------------------------------------------------------------------------------- /docs/assets/images/geo-logo-white.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 9 | 12 | 16 | 20 | 24 | 28 | 30 | 33 | 35 | 39 | 40 | 41 | 44 | 47 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /docs/assets/images/layout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geobeyond/geoserverx/64349483b643d4c662da9d358286728360a38015/docs/assets/images/layout.png -------------------------------------------------------------------------------- /docs/assets/images/new_shp_vector.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geobeyond/geoserverx/64349483b643d4c662da9d358286728360a38015/docs/assets/images/new_shp_vector.png -------------------------------------------------------------------------------- /docs/assets/images/workspace_created.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geobeyond/geoserverx/64349483b643d4c662da9d358286728360a38015/docs/assets/images/workspace_created.png -------------------------------------------------------------------------------- /docs/assets/javascripts/custom.js: -------------------------------------------------------------------------------- 1 | const div = document.querySelector('.github-topic-projects') 2 | 3 | async function getDataBatch(page) { 4 | const response = await fetch(`https://api.github.com/search/repositories?q=topic:fastapi&per_page=100&page=${page}`, { headers: { Accept: 'application/vnd.github.mercy-preview+json' } }) 5 | const data = await response.json() 6 | return data 7 | } 8 | 9 | async function getData() { 10 | let page = 1 11 | let data = [] 12 | let dataBatch = await getDataBatch(page) 13 | data = data.concat(dataBatch.items) 14 | const totalCount = dataBatch.total_count 15 | while (data.length < totalCount) { 16 | page += 1 17 | dataBatch = await getDataBatch(page) 18 | data = data.concat(dataBatch.items) 19 | } 20 | return data 21 | } 22 | 23 | function setupTermynal() { 24 | document.querySelectorAll(".use-termynal").forEach(node => { 25 | node.style.display = "block"; 26 | new Termynal(node, { 27 | lineDelay: 500 28 | }); 29 | }); 30 | const progressLiteralStart = "---> 100%"; 31 | const promptLiteralStart = "$ "; 32 | const customPromptLiteralStart = "# "; 33 | const termynalActivateClass = "termy"; 34 | let termynals = []; 35 | 36 | function createTermynals() { 37 | document 38 | .querySelectorAll(`.${termynalActivateClass} .highlight`) 39 | .forEach(node => { 40 | const text = node.textContent; 41 | const lines = text.split("\n"); 42 | const useLines = []; 43 | let buffer = []; 44 | function saveBuffer() { 45 | if (buffer.length) { 46 | let isBlankSpace = true; 47 | buffer.forEach(line => { 48 | if (line) { 49 | isBlankSpace = false; 50 | } 51 | }); 52 | dataValue = {}; 53 | if (isBlankSpace) { 54 | dataValue["delay"] = 0; 55 | } 56 | if (buffer[buffer.length - 1] === "") { 57 | // A last single
won't have effect 58 | // so put an additional one 59 | buffer.push(""); 60 | } 61 | const bufferValue = buffer.join("
"); 62 | dataValue["value"] = bufferValue; 63 | useLines.push(dataValue); 64 | buffer = []; 65 | } 66 | } 67 | for (let line of lines) { 68 | if (line === progressLiteralStart) { 69 | saveBuffer(); 70 | useLines.push({ 71 | type: "progress" 72 | }); 73 | } else if (line.startsWith(promptLiteralStart)) { 74 | saveBuffer(); 75 | const value = line.replace(promptLiteralStart, "").trimEnd(); 76 | useLines.push({ 77 | type: "input", 78 | value: value 79 | }); 80 | } else if (line.startsWith("// ")) { 81 | saveBuffer(); 82 | const value = "💬 " + line.replace("// ", "").trimEnd(); 83 | useLines.push({ 84 | value: value, 85 | class: "termynal-comment", 86 | delay: 0 87 | }); 88 | } else if (line.startsWith(customPromptLiteralStart)) { 89 | saveBuffer(); 90 | const promptStart = line.indexOf(promptLiteralStart); 91 | if (promptStart === -1) { 92 | console.error("Custom prompt found but no end delimiter", line) 93 | } 94 | const prompt = line.slice(0, promptStart).replace(customPromptLiteralStart, "") 95 | let value = line.slice(promptStart + promptLiteralStart.length); 96 | useLines.push({ 97 | type: "input", 98 | value: value, 99 | prompt: prompt 100 | }); 101 | } else { 102 | buffer.push(line); 103 | } 104 | } 105 | saveBuffer(); 106 | const div = document.createElement("div"); 107 | node.replaceWith(div); 108 | const termynal = new Termynal(div, { 109 | lineData: useLines, 110 | noInit: true, 111 | lineDelay: 500 112 | }); 113 | termynals.push(termynal); 114 | }); 115 | } 116 | 117 | function loadVisibleTermynals() { 118 | termynals = termynals.filter(termynal => { 119 | if (termynal.container.getBoundingClientRect().top - innerHeight <= 0) { 120 | termynal.init(); 121 | return false; 122 | } 123 | return true; 124 | }); 125 | } 126 | window.addEventListener("scroll", loadVisibleTermynals); 127 | createTermynals(); 128 | loadVisibleTermynals(); 129 | } 130 | 131 | function shuffle(array) { 132 | var currentIndex = array.length, temporaryValue, randomIndex; 133 | while (0 !== currentIndex) { 134 | randomIndex = Math.floor(Math.random() * currentIndex); 135 | currentIndex -= 1; 136 | temporaryValue = array[currentIndex]; 137 | array[currentIndex] = array[randomIndex]; 138 | array[randomIndex] = temporaryValue; 139 | } 140 | return array; 141 | } 142 | 143 | async function showRandomAnnouncement(groupId, timeInterval) { 144 | const announceFastAPI = document.getElementById(groupId); 145 | if (announceFastAPI) { 146 | let children = [].slice.call(announceFastAPI.children); 147 | children = shuffle(children) 148 | let index = 0 149 | const announceRandom = () => { 150 | children.forEach((el, i) => {el.style.display = "none"}); 151 | children[index].style.display = "block" 152 | index = (index + 1) % children.length 153 | } 154 | announceRandom() 155 | setInterval(announceRandom, timeInterval 156 | ) 157 | } 158 | } 159 | 160 | async function main() { 161 | if (div) { 162 | data = await getData() 163 | div.innerHTML = '' 164 | const ul = document.querySelector('.github-topic-projects ul') 165 | data.forEach(v => { 166 | if (v.full_name === 'tiangolo/fastapi') { 167 | return 168 | } 169 | const li = document.createElement('li') 170 | li.innerHTML = `★ ${v.stargazers_count} - ${v.full_name} by @${v.owner.login}` 171 | ul.append(li) 172 | }) 173 | } 174 | 175 | setupTermynal(); 176 | showRandomAnnouncement('announce-left', 5000) 177 | showRandomAnnouncement('announce-right', 10000) 178 | } 179 | 180 | main() 181 | -------------------------------------------------------------------------------- /docs/assets/javascripts/old_custom.js: -------------------------------------------------------------------------------- 1 | document.querySelectorAll(".use-termynal").forEach(node => { 2 | node.style.display = "block"; 3 | new Termynal(node, { 4 | lineDelay: 1500 5 | }); 6 | }); 7 | const progressLiteralStart = "---> 100%"; 8 | const promptLiteralStart = "$ "; 9 | const customPromptLiteralStart = "# "; 10 | const termynalActivateClass = "termy"; 11 | let termynals = []; 12 | 13 | function createTermynals() { 14 | document 15 | .querySelectorAll(`.${termynalActivateClass} .highlight`) 16 | .forEach(node => { 17 | const text = node.textContent; 18 | const lines = text.split("\n"); 19 | const useLines = []; 20 | let buffer = []; 21 | function saveBuffer() { 22 | if (buffer.length) { 23 | let isBlankSpace = true; 24 | buffer.forEach(line => { 25 | if (line) { 26 | isBlankSpace = false; 27 | } 28 | }); 29 | dataValue = {}; 30 | if (isBlankSpace) { 31 | dataValue["delay"] = 0; 32 | } 33 | if (buffer[buffer.length - 1] === "") { 34 | // A last single
won't have effect 35 | // so put an additional one 36 | buffer.push(""); 37 | } 38 | const bufferValue = buffer.join("
"); 39 | dataValue["value"] = bufferValue; 40 | useLines.push(dataValue); 41 | buffer = []; 42 | } 43 | } 44 | for (let line of lines) { 45 | if (line === progressLiteralStart) { 46 | saveBuffer(); 47 | useLines.push({ 48 | type: "progress" 49 | }); 50 | } else if (line.startsWith(promptLiteralStart)) { 51 | saveBuffer(); 52 | const value = line.replace(promptLiteralStart, "").trimEnd(); 53 | useLines.push({ 54 | type: "input", 55 | value: value 56 | }); 57 | } else if (line.startsWith("// ")) { 58 | saveBuffer(); 59 | const value = "💬 " + line.replace("// ", "").trimEnd(); 60 | useLines.push({ 61 | value: value, 62 | class: "termynal-comment", 63 | delay: 0 64 | }); 65 | } else if (line.startsWith(customPromptLiteralStart)) { 66 | saveBuffer(); 67 | const promptStart = line.indexOf(promptLiteralStart); 68 | if (promptStart === -1) { 69 | console.error("Custom prompt found but no end delimiter", line) 70 | } 71 | const prompt = line.slice(0, promptStart).replace(customPromptLiteralStart, "") 72 | let value = line.slice(promptStart + promptLiteralStart.length); 73 | useLines.push({ 74 | type: "input", 75 | value: value, 76 | prompt: prompt 77 | }); 78 | } else { 79 | buffer.push(line); 80 | } 81 | } 82 | saveBuffer(); 83 | const div = document.createElement("div"); 84 | node.replaceWith(div); 85 | const termynal = new Termynal(div, { 86 | lineData: useLines, 87 | noInit: true, 88 | lineDelay: 500 89 | }); 90 | termynals.push(termynal); 91 | }); 92 | } 93 | 94 | function loadVisibleTermynals() { 95 | termynals = termynals.filter(termynal => { 96 | if (termynal.container.getBoundingClientRect().top - innerHeight <= 0) { 97 | termynal.init(); 98 | return false; 99 | } 100 | return true; 101 | }); 102 | } 103 | window.addEventListener("scroll", loadVisibleTermynals); 104 | createTermynals(); 105 | loadVisibleTermynals(); -------------------------------------------------------------------------------- /docs/assets/javascripts/old_termynal.js: -------------------------------------------------------------------------------- 1 | /** 2 | * termynal.js 3 | * A lightweight, modern and extensible animated terminal window, using 4 | * async/await. 5 | * 6 | * @author Ines Montani 7 | * @version 0.0.1 8 | * @license MIT 9 | */ 10 | 11 | 'use strict'; 12 | 13 | /** Generate a terminal widget. */ 14 | class Termynal { 15 | /** 16 | * Construct the widget's settings. 17 | * @param {(string|Node)=} container - Query selector or container element. 18 | * @param {Object=} options - Custom settings. 19 | * @param {string} options.prefix - Prefix to use for data attributes. 20 | * @param {number} options.startDelay - Delay before animation, in ms. 21 | * @param {number} options.typeDelay - Delay between each typed character, in ms. 22 | * @param {number} options.lineDelay - Delay between each line, in ms. 23 | * @param {number} options.progressLength - Number of characters displayed as progress bar. 24 | * @param {string} options.progressChar – Character to use for progress bar, defaults to █. 25 | * @param {number} options.progressPercent - Max percent of progress. 26 | * @param {string} options.cursor – Character to use for cursor, defaults to ▋. 27 | * @param {Object[]} lineData - Dynamically loaded line data objects. 28 | * @param {boolean} options.noInit - Don't initialise the animation. 29 | */ 30 | constructor(container = '#termynal', options = {}) { 31 | this.container = (typeof container === 'string') ? document.querySelector(container) : container; 32 | this.pfx = `data-${options.prefix || 'ty'}`; 33 | this.originalStartDelay = this.startDelay = options.startDelay 34 | || parseFloat(this.container.getAttribute(`${this.pfx}-startDelay`)) || 600; 35 | this.originalTypeDelay = this.typeDelay = options.typeDelay 36 | || parseFloat(this.container.getAttribute(`${this.pfx}-typeDelay`)) || 90; 37 | this.originalLineDelay = this.lineDelay = options.lineDelay 38 | || parseFloat(this.container.getAttribute(`${this.pfx}-lineDelay`)) || 1500; 39 | this.progressLength = options.progressLength 40 | || parseFloat(this.container.getAttribute(`${this.pfx}-progressLength`)) || 40; 41 | this.progressChar = options.progressChar 42 | || this.container.getAttribute(`${this.pfx}-progressChar`) || '█'; 43 | this.progressPercent = options.progressPercent 44 | || parseFloat(this.container.getAttribute(`${this.pfx}-progressPercent`)) || 100; 45 | this.cursor = options.cursor 46 | || this.container.getAttribute(`${this.pfx}-cursor`) || '▋'; 47 | this.lineData = this.lineDataToElements(options.lineData || []); 48 | this.loadLines() 49 | if (!options.noInit) this.init() 50 | } 51 | 52 | loadLines() { 53 | // Load all the lines and create the container so that the size is fixed 54 | // Otherwise it would be changing and the user viewport would be constantly 55 | // moving as she/he scrolls 56 | const finish = this.generateFinish() 57 | finish.style.visibility = 'hidden' 58 | this.container.appendChild(finish) 59 | // Appends dynamically loaded lines to existing line elements. 60 | this.lines = [...this.container.querySelectorAll(`[${this.pfx}]`)].concat(this.lineData); 61 | for (let line of this.lines) { 62 | line.style.visibility = 'hidden' 63 | this.container.appendChild(line) 64 | } 65 | const restart = this.generateRestart() 66 | restart.style.visibility = 'hidden' 67 | this.container.appendChild(restart) 68 | this.container.setAttribute('data-termynal', ''); 69 | } 70 | 71 | /** 72 | * Initialise the widget, get lines, clear container and start animation. 73 | */ 74 | init() { 75 | /** 76 | * Calculates width and height of Termynal container. 77 | * If container is empty and lines are dynamically loaded, defaults to browser `auto` or CSS. 78 | */ 79 | const containerStyle = getComputedStyle(this.container); 80 | this.container.style.width = containerStyle.width !== '0px' ? 81 | containerStyle.width : undefined; 82 | this.container.style.minHeight = containerStyle.height !== '0px' ? 83 | containerStyle.height : undefined; 84 | 85 | this.container.setAttribute('data-termynal', ''); 86 | this.container.innerHTML = ''; 87 | for (let line of this.lines) { 88 | line.style.visibility = 'visible' 89 | } 90 | this.start(); 91 | } 92 | 93 | /** 94 | * Start the animation and rener the lines depending on their data attributes. 95 | */ 96 | async start() { 97 | this.addFinish() 98 | await this._wait(this.startDelay); 99 | 100 | for (let line of this.lines) { 101 | const type = line.getAttribute(this.pfx); 102 | const delay = line.getAttribute(`${this.pfx}-delay`) || this.lineDelay; 103 | 104 | if (type == 'input') { 105 | line.setAttribute(`${this.pfx}-cursor`, this.cursor); 106 | await this.type(line); 107 | await this._wait(delay); 108 | } 109 | 110 | else if (type == 'progress') { 111 | await this.progress(line); 112 | await this._wait(delay); 113 | } 114 | 115 | else { 116 | this.container.appendChild(line); 117 | await this._wait(delay); 118 | } 119 | 120 | line.removeAttribute(`${this.pfx}-cursor`); 121 | } 122 | this.addRestart() 123 | this.finishElement.style.visibility = 'hidden' 124 | this.lineDelay = this.originalLineDelay 125 | this.typeDelay = this.originalTypeDelay 126 | this.startDelay = this.originalStartDelay 127 | } 128 | 129 | generateRestart() { 130 | const restart = document.createElement('a') 131 | restart.onclick = (e) => { 132 | e.preventDefault() 133 | this.container.innerHTML = '' 134 | this.init() 135 | } 136 | restart.href = '#' 137 | restart.setAttribute('data-terminal-control', '') 138 | restart.innerHTML = "restart ↻" 139 | return restart 140 | } 141 | 142 | generateFinish() { 143 | const finish = document.createElement('a') 144 | finish.onclick = (e) => { 145 | e.preventDefault() 146 | this.lineDelay = 0 147 | this.typeDelay = 0 148 | this.startDelay = 0 149 | } 150 | finish.href = '#' 151 | finish.setAttribute('data-terminal-control', '') 152 | finish.innerHTML = "fast →" 153 | this.finishElement = finish 154 | return finish 155 | } 156 | 157 | addRestart() { 158 | const restart = this.generateRestart() 159 | this.container.appendChild(restart) 160 | } 161 | 162 | addFinish() { 163 | const finish = this.generateFinish() 164 | this.container.appendChild(finish) 165 | } 166 | 167 | /** 168 | * Animate a typed line. 169 | * @param {Node} line - The line element to render. 170 | */ 171 | async type(line) { 172 | const chars = [...line.textContent]; 173 | line.textContent = ''; 174 | this.container.appendChild(line); 175 | 176 | for (let char of chars) { 177 | const delay = line.getAttribute(`${this.pfx}-typeDelay`) || this.typeDelay; 178 | await this._wait(delay); 179 | line.textContent += char; 180 | } 181 | } 182 | 183 | /** 184 | * Animate a progress bar. 185 | * @param {Node} line - The line element to render. 186 | */ 187 | async progress(line) { 188 | const progressLength = line.getAttribute(`${this.pfx}-progressLength`) 189 | || this.progressLength; 190 | const progressChar = line.getAttribute(`${this.pfx}-progressChar`) 191 | || this.progressChar; 192 | const chars = progressChar.repeat(progressLength); 193 | const progressPercent = line.getAttribute(`${this.pfx}-progressPercent`) 194 | || this.progressPercent; 195 | line.textContent = ''; 196 | this.container.appendChild(line); 197 | 198 | for (let i = 1; i < chars.length + 1; i++) { 199 | await this._wait(this.typeDelay); 200 | const percent = Math.round(i / chars.length * 100); 201 | line.textContent = `${chars.slice(0, i)} ${percent}%`; 202 | if (percent>progressPercent) { 203 | break; 204 | } 205 | } 206 | } 207 | 208 | /** 209 | * Helper function for animation delays, called with `await`. 210 | * @param {number} time - Timeout, in ms. 211 | */ 212 | _wait(time) { 213 | return new Promise(resolve => setTimeout(resolve, time)); 214 | } 215 | 216 | /** 217 | * Converts line data objects into line elements. 218 | * 219 | * @param {Object[]} lineData - Dynamically loaded lines. 220 | * @param {Object} line - Line data object. 221 | * @returns {Element[]} - Array of line elements. 222 | */ 223 | lineDataToElements(lineData) { 224 | return lineData.map(line => { 225 | let div = document.createElement('div'); 226 | div.innerHTML = `${line.value || ''}`; 227 | 228 | return div.firstElementChild; 229 | }); 230 | } 231 | 232 | /** 233 | * Helper function for generating attributes string. 234 | * 235 | * @param {Object} line - Line data object. 236 | * @returns {string} - String of attributes. 237 | */ 238 | _attributes(line) { 239 | let attrs = ''; 240 | for (let prop in line) { 241 | // Custom add class 242 | if (prop === 'class') { 243 | attrs += ` class=${line[prop]} ` 244 | continue 245 | } 246 | if (prop === 'type') { 247 | attrs += `${this.pfx}="${line[prop]}" ` 248 | } else if (prop !== 'value') { 249 | attrs += `${this.pfx}-${prop}="${line[prop]}" ` 250 | } 251 | } 252 | 253 | return attrs; 254 | } 255 | } 256 | 257 | /** 258 | * HTML API: If current script has container(s) specified, initialise Termynal. 259 | */ 260 | if (document.currentScript.hasAttribute('data-termynal-container')) { 261 | const containers = document.currentScript.getAttribute('data-termynal-container'); 262 | containers.split('|') 263 | .forEach(container => new Termynal(container)) 264 | } -------------------------------------------------------------------------------- /docs/assets/javascripts/termynal.js: -------------------------------------------------------------------------------- 1 | /** 2 | * termynal.js 3 | * A lightweight, modern and extensible animated terminal window, using 4 | * async/await. 5 | * 6 | * @author Ines Montani 7 | * @version 0.0.1 8 | * @license MIT 9 | */ 10 | 11 | 'use strict'; 12 | 13 | /** Generate a terminal widget. */ 14 | class Termynal { 15 | /** 16 | * Construct the widget's settings. 17 | * @param {(string|Node)=} container - Query selector or container element. 18 | * @param {Object=} options - Custom settings. 19 | * @param {string} options.prefix - Prefix to use for data attributes. 20 | * @param {number} options.startDelay - Delay before animation, in ms. 21 | * @param {number} options.typeDelay - Delay between each typed character, in ms. 22 | * @param {number} options.lineDelay - Delay between each line, in ms. 23 | * @param {number} options.progressLength - Number of characters displayed as progress bar. 24 | * @param {string} options.progressChar – Character to use for progress bar, defaults to █. 25 | * @param {number} options.progressPercent - Max percent of progress. 26 | * @param {string} options.cursor – Character to use for cursor, defaults to ▋. 27 | * @param {Object[]} lineData - Dynamically loaded line data objects. 28 | * @param {boolean} options.noInit - Don't initialise the animation. 29 | */ 30 | constructor(container = '#termynal', options = {}) { 31 | this.container = (typeof container === 'string') ? document.querySelector(container) : container; 32 | this.pfx = `data-${options.prefix || 'ty'}`; 33 | this.originalStartDelay = this.startDelay = options.startDelay 34 | || parseFloat(this.container.getAttribute(`${this.pfx}-startDelay`)) || 600; 35 | this.originalTypeDelay = this.typeDelay = options.typeDelay 36 | || parseFloat(this.container.getAttribute(`${this.pfx}-typeDelay`)) || 90; 37 | this.originalLineDelay = this.lineDelay = options.lineDelay 38 | || parseFloat(this.container.getAttribute(`${this.pfx}-lineDelay`)) || 1500; 39 | this.progressLength = options.progressLength 40 | || parseFloat(this.container.getAttribute(`${this.pfx}-progressLength`)) || 40; 41 | this.progressChar = options.progressChar 42 | || this.container.getAttribute(`${this.pfx}-progressChar`) || '█'; 43 | this.progressPercent = options.progressPercent 44 | || parseFloat(this.container.getAttribute(`${this.pfx}-progressPercent`)) || 100; 45 | this.cursor = options.cursor 46 | || this.container.getAttribute(`${this.pfx}-cursor`) || '▋'; 47 | this.lineData = this.lineDataToElements(options.lineData || []); 48 | this.loadLines() 49 | if (!options.noInit) this.init() 50 | } 51 | 52 | loadLines() { 53 | // Load all the lines and create the container so that the size is fixed 54 | // Otherwise it would be changing and the user viewport would be constantly 55 | // moving as she/he scrolls 56 | const finish = this.generateFinish() 57 | finish.style.visibility = 'hidden' 58 | this.container.appendChild(finish) 59 | // Appends dynamically loaded lines to existing line elements. 60 | this.lines = [...this.container.querySelectorAll(`[${this.pfx}]`)].concat(this.lineData); 61 | for (let line of this.lines) { 62 | line.style.visibility = 'hidden' 63 | this.container.appendChild(line) 64 | } 65 | const restart = this.generateRestart() 66 | restart.style.visibility = 'hidden' 67 | this.container.appendChild(restart) 68 | this.container.setAttribute('data-termynal', ''); 69 | } 70 | 71 | /** 72 | * Initialise the widget, get lines, clear container and start animation. 73 | */ 74 | init() { 75 | /** 76 | * Calculates width and height of Termynal container. 77 | * If container is empty and lines are dynamically loaded, defaults to browser `auto` or CSS. 78 | */ 79 | const containerStyle = getComputedStyle(this.container); 80 | this.container.style.width = containerStyle.width !== '0px' ? 81 | containerStyle.width : undefined; 82 | this.container.style.minHeight = containerStyle.height !== '0px' ? 83 | containerStyle.height : undefined; 84 | 85 | this.container.setAttribute('data-termynal', ''); 86 | this.container.innerHTML = ''; 87 | for (let line of this.lines) { 88 | line.style.visibility = 'visible' 89 | } 90 | this.start(); 91 | } 92 | 93 | /** 94 | * Start the animation and rener the lines depending on their data attributes. 95 | */ 96 | async start() { 97 | this.addFinish() 98 | await this._wait(this.startDelay); 99 | 100 | for (let line of this.lines) { 101 | const type = line.getAttribute(this.pfx); 102 | const delay = line.getAttribute(`${this.pfx}-delay`) || this.lineDelay; 103 | 104 | if (type == 'input') { 105 | line.setAttribute(`${this.pfx}-cursor`, this.cursor); 106 | await this.type(line); 107 | await this._wait(delay); 108 | } 109 | 110 | else if (type == 'progress') { 111 | await this.progress(line); 112 | await this._wait(delay); 113 | } 114 | 115 | else { 116 | this.container.appendChild(line); 117 | await this._wait(delay); 118 | } 119 | 120 | line.removeAttribute(`${this.pfx}-cursor`); 121 | } 122 | this.addRestart() 123 | this.finishElement.style.visibility = 'hidden' 124 | this.lineDelay = this.originalLineDelay 125 | this.typeDelay = this.originalTypeDelay 126 | this.startDelay = this.originalStartDelay 127 | } 128 | 129 | generateRestart() { 130 | const restart = document.createElement('a') 131 | restart.onclick = (e) => { 132 | e.preventDefault() 133 | this.container.innerHTML = '' 134 | this.init() 135 | } 136 | restart.href = '#' 137 | restart.setAttribute('data-terminal-control', '') 138 | restart.innerHTML = "restart ↻" 139 | return restart 140 | } 141 | 142 | generateFinish() { 143 | const finish = document.createElement('a') 144 | finish.onclick = (e) => { 145 | e.preventDefault() 146 | this.lineDelay = 0 147 | this.typeDelay = 0 148 | this.startDelay = 0 149 | } 150 | finish.href = '#' 151 | finish.setAttribute('data-terminal-control', '') 152 | finish.innerHTML = "fast →" 153 | this.finishElement = finish 154 | return finish 155 | } 156 | 157 | addRestart() { 158 | const restart = this.generateRestart() 159 | this.container.appendChild(restart) 160 | } 161 | 162 | addFinish() { 163 | const finish = this.generateFinish() 164 | this.container.appendChild(finish) 165 | } 166 | 167 | /** 168 | * Animate a typed line. 169 | * @param {Node} line - The line element to render. 170 | */ 171 | async type(line) { 172 | const chars = [...line.textContent]; 173 | line.textContent = ''; 174 | this.container.appendChild(line); 175 | 176 | for (let char of chars) { 177 | const delay = line.getAttribute(`${this.pfx}-typeDelay`) || this.typeDelay; 178 | await this._wait(delay); 179 | line.textContent += char; 180 | } 181 | } 182 | 183 | /** 184 | * Animate a progress bar. 185 | * @param {Node} line - The line element to render. 186 | */ 187 | async progress(line) { 188 | const progressLength = line.getAttribute(`${this.pfx}-progressLength`) 189 | || this.progressLength; 190 | const progressChar = line.getAttribute(`${this.pfx}-progressChar`) 191 | || this.progressChar; 192 | const chars = progressChar.repeat(progressLength); 193 | const progressPercent = line.getAttribute(`${this.pfx}-progressPercent`) 194 | || this.progressPercent; 195 | line.textContent = ''; 196 | this.container.appendChild(line); 197 | 198 | for (let i = 1; i < chars.length + 1; i++) { 199 | await this._wait(this.typeDelay); 200 | const percent = Math.round(i / chars.length * 100); 201 | line.textContent = `${chars.slice(0, i)} ${percent}%`; 202 | if (percent>progressPercent) { 203 | break; 204 | } 205 | } 206 | } 207 | 208 | /** 209 | * Helper function for animation delays, called with `await`. 210 | * @param {number} time - Timeout, in ms. 211 | */ 212 | _wait(time) { 213 | return new Promise(resolve => setTimeout(resolve, time)); 214 | } 215 | 216 | /** 217 | * Converts line data objects into line elements. 218 | * 219 | * @param {Object[]} lineData - Dynamically loaded lines. 220 | * @param {Object} line - Line data object. 221 | * @returns {Element[]} - Array of line elements. 222 | */ 223 | lineDataToElements(lineData) { 224 | return lineData.map(line => { 225 | let div = document.createElement('div'); 226 | div.innerHTML = `${line.value || ''}`; 227 | 228 | return div.firstElementChild; 229 | }); 230 | } 231 | 232 | /** 233 | * Helper function for generating attributes string. 234 | * 235 | * @param {Object} line - Line data object. 236 | * @returns {string} - String of attributes. 237 | */ 238 | _attributes(line) { 239 | let attrs = ''; 240 | for (let prop in line) { 241 | // Custom add class 242 | if (prop === 'class') { 243 | attrs += ` class=${line[prop]} ` 244 | continue 245 | } 246 | if (prop === 'type') { 247 | attrs += `${this.pfx}="${line[prop]}" ` 248 | } else if (prop !== 'value') { 249 | attrs += `${this.pfx}-${prop}="${line[prop]}" ` 250 | } 251 | } 252 | 253 | return attrs; 254 | } 255 | } 256 | 257 | /** 258 | * HTML API: If current script has container(s) specified, initialise Termynal. 259 | */ 260 | if (document.currentScript.hasAttribute('data-termynal-container')) { 261 | const containers = document.currentScript.getAttribute('data-termynal-container'); 262 | containers.split('|') 263 | .forEach(container => new Termynal(container)) 264 | } 265 | -------------------------------------------------------------------------------- /docs/assets/stylesheets/custom.css: -------------------------------------------------------------------------------- 1 | .termynal-comment { 2 | color: #4a968f; 3 | font-style: italic; 4 | display: block; 5 | } 6 | 7 | .termy { 8 | /* For right to left languages */ 9 | direction: ltr; 10 | } 11 | 12 | .termy [data-termynal] { 13 | white-space: pre-wrap; 14 | } 15 | 16 | a.external-link { 17 | /* For right to left languages */ 18 | direction: ltr; 19 | display: inline-block; 20 | } 21 | 22 | a.external-link::after { 23 | /* \00A0 is a non-breaking space 24 | to make the mark be on the same line as the link 25 | */ 26 | content: "\00A0[↪]"; 27 | } 28 | 29 | a.internal-link::after { 30 | /* \00A0 is a non-breaking space 31 | to make the mark be on the same line as the link 32 | */ 33 | content: "\00A0↪"; 34 | } 35 | 36 | .shadow { 37 | box-shadow: 5px 5px 10px #999; 38 | } 39 | 40 | /* Give space to lower icons so Gitter chat doesn't get on top of them */ 41 | .md-footer-meta { 42 | padding-bottom: 2em; 43 | } 44 | 45 | .user-list { 46 | display: flex; 47 | flex-wrap: wrap; 48 | margin-bottom: 2rem; 49 | } 50 | 51 | .user-list-center { 52 | justify-content: space-evenly; 53 | } 54 | 55 | .user { 56 | margin: 1em; 57 | min-width: 7em; 58 | } 59 | 60 | .user .avatar-wrapper { 61 | width: 80px; 62 | height: 80px; 63 | margin: 10px auto; 64 | overflow: hidden; 65 | border-radius: 50%; 66 | position: relative; 67 | } 68 | 69 | .user .avatar-wrapper img { 70 | position: absolute; 71 | top: 50%; 72 | left: 50%; 73 | transform: translate(-50%, -50%); 74 | } 75 | 76 | .user .title { 77 | text-align: center; 78 | } 79 | 80 | .user .count { 81 | font-size: 80%; 82 | text-align: center; 83 | } 84 | 85 | a.announce-link:link, 86 | a.announce-link:visited { 87 | color: #fff; 88 | } 89 | 90 | a.announce-link:hover { 91 | color: var(--md-accent-fg-color); 92 | } 93 | 94 | .announce-wrapper { 95 | display: flex; 96 | justify-content: space-between; 97 | flex-wrap: wrap; 98 | align-items: center; 99 | } 100 | 101 | .announce-wrapper div.item { 102 | display: none; 103 | } 104 | 105 | .announce-wrapper .sponsor-badge { 106 | display: block; 107 | position: absolute; 108 | top: -10px; 109 | right: 0; 110 | font-size: 0.5rem; 111 | color: #999; 112 | background-color: #666; 113 | border-radius: 10px; 114 | padding: 0 10px; 115 | z-index: 10; 116 | } 117 | 118 | .announce-wrapper .sponsor-image { 119 | display: block; 120 | border-radius: 20px; 121 | } 122 | 123 | .announce-wrapper>div { 124 | min-height: 40px; 125 | display: flex; 126 | align-items: center; 127 | } 128 | 129 | .twitter { 130 | color: #00acee; 131 | } 132 | 133 | /* Right to left languages */ 134 | code { 135 | direction: ltr; 136 | display: inline-block; 137 | } 138 | 139 | .md-content__inner h1 { 140 | direction: ltr !important; 141 | } 142 | 143 | .illustration { 144 | margin-top: 2em; 145 | margin-bottom: 2em; 146 | } 147 | -------------------------------------------------------------------------------- /docs/assets/stylesheets/old_custom.css: -------------------------------------------------------------------------------- 1 | .termynal-comment { 2 | color: #4a968f; 3 | font-style: italic; 4 | display: block; 5 | } 6 | 7 | .termy [data-termynal] { 8 | white-space: pre-wrap; 9 | } 10 | 11 | a.external-link::after { 12 | /* \00A0 is a non-breaking space 13 | to make the mark be on the same line as the link 14 | */ 15 | content: "\00A0[↪]"; 16 | } 17 | 18 | a.internal-link::after { 19 | /* \00A0 is a non-breaking space 20 | to make the mark be on the same line as the link 21 | */ 22 | content: "\00A0↪"; 23 | } 24 | -------------------------------------------------------------------------------- /docs/assets/stylesheets/old_termynal.css: -------------------------------------------------------------------------------- 1 | /** 2 | * termynal.js 3 | * 4 | * @author Ines Montani 5 | * @version 0.0.1 6 | * @license MIT 7 | */ 8 | 9 | :root { 10 | --color-bg: #252a33; 11 | --color-text: #eee; 12 | --color-text-subtle: #a2a2a2; 13 | } 14 | 15 | [data-termynal] { 16 | width: 750px; 17 | max-width: 100%; 18 | background: var(--color-bg); 19 | color: var(--color-text); 20 | font-size: 18px; 21 | /* font-family: 'Fira Mono', Consolas, Menlo, Monaco, 'Courier New', Courier, monospace; */ 22 | font-family: 'Roboto Mono', 'Fira Mono', Consolas, Menlo, Monaco, 'Courier New', Courier, monospace; 23 | border-radius: 4px; 24 | padding: 75px 45px 35px; 25 | position: relative; 26 | -webkit-box-sizing: border-box; 27 | box-sizing: border-box; 28 | } 29 | 30 | [data-termynal]:before { 31 | content: ''; 32 | position: absolute; 33 | top: 15px; 34 | left: 15px; 35 | display: inline-block; 36 | width: 15px; 37 | height: 15px; 38 | border-radius: 50%; 39 | /* A little hack to display the window buttons in one pseudo element. */ 40 | background: #d9515d; 41 | -webkit-box-shadow: 25px 0 0 #f4c025, 50px 0 0 #3ec930; 42 | box-shadow: 25px 0 0 #f4c025, 50px 0 0 #3ec930; 43 | } 44 | 45 | [data-termynal]:after { 46 | content: 'Terminal'; 47 | position: absolute; 48 | color: var(--color-text-subtle); 49 | top: 5px; 50 | left: 0; 51 | width: 100%; 52 | text-align: center; 53 | } 54 | 55 | a[data-terminal-control] { 56 | text-align: right; 57 | display: block; 58 | color: #aebbff; 59 | } 60 | 61 | [data-ty] { 62 | display: block; 63 | line-height: 2; 64 | } 65 | 66 | [data-ty]:before { 67 | /* Set up defaults and ensure empty lines are displayed. */ 68 | content: ''; 69 | display: inline-block; 70 | vertical-align: middle; 71 | } 72 | 73 | [data-ty="input"]:before, 74 | [data-ty-prompt]:before { 75 | margin-right: 0.75em; 76 | color: var(--color-text-subtle); 77 | } 78 | 79 | [data-ty="input"]:before { 80 | content: '$'; 81 | } 82 | 83 | [data-ty][data-ty-prompt]:before { 84 | content: attr(data-ty-prompt); 85 | } 86 | 87 | [data-ty-cursor]:after { 88 | content: attr(data-ty-cursor); 89 | font-family: monospace; 90 | margin-left: 0.5em; 91 | -webkit-animation: blink 1s infinite; 92 | animation: blink 1s infinite; 93 | } 94 | 95 | 96 | /* Cursor animation */ 97 | 98 | @-webkit-keyframes blink { 99 | 50% { 100 | opacity: 0; 101 | } 102 | } 103 | 104 | @keyframes blink { 105 | 50% { 106 | opacity: 0; 107 | } 108 | } -------------------------------------------------------------------------------- /docs/assets/stylesheets/termynal.css: -------------------------------------------------------------------------------- 1 | /** 2 | * termynal.js 3 | * 4 | * @author Ines Montani 5 | * @version 0.0.1 6 | * @license MIT 7 | */ 8 | 9 | :root { 10 | --color-bg: #252a33; 11 | --color-text: #eee; 12 | --color-text-subtle: #a2a2a2; 13 | } 14 | 15 | [data-termynal] { 16 | width: 750px; 17 | max-width: 100%; 18 | background: var(--color-bg); 19 | color: var(--color-text); 20 | /* font-size: 18px; */ 21 | font-size: 15px; 22 | /* font-family: 'Fira Mono', Consolas, Menlo, Monaco, 'Courier New', Courier, monospace; */ 23 | font-family: 'Roboto Mono', 'Fira Mono', Consolas, Menlo, Monaco, 'Courier New', Courier, monospace; 24 | border-radius: 4px; 25 | padding: 75px 45px 35px; 26 | position: relative; 27 | -webkit-box-sizing: border-box; 28 | box-sizing: border-box; 29 | } 30 | 31 | [data-termynal]:before { 32 | content: ''; 33 | position: absolute; 34 | top: 15px; 35 | left: 15px; 36 | display: inline-block; 37 | width: 15px; 38 | height: 15px; 39 | border-radius: 50%; 40 | /* A little hack to display the window buttons in one pseudo element. */ 41 | background: #d9515d; 42 | -webkit-box-shadow: 25px 0 0 #f4c025, 50px 0 0 #3ec930; 43 | box-shadow: 25px 0 0 #f4c025, 50px 0 0 #3ec930; 44 | } 45 | 46 | [data-termynal]:after { 47 | content: 'bash'; 48 | position: absolute; 49 | color: var(--color-text-subtle); 50 | top: 5px; 51 | left: 0; 52 | width: 100%; 53 | text-align: center; 54 | } 55 | 56 | a[data-terminal-control] { 57 | text-align: right; 58 | display: block; 59 | color: #aebbff; 60 | } 61 | 62 | [data-ty] { 63 | display: block; 64 | line-height: 2; 65 | } 66 | 67 | [data-ty]:before { 68 | /* Set up defaults and ensure empty lines are displayed. */ 69 | content: ''; 70 | display: inline-block; 71 | vertical-align: middle; 72 | } 73 | 74 | [data-ty="input"]:before, 75 | [data-ty-prompt]:before { 76 | margin-right: 0.75em; 77 | color: var(--color-text-subtle); 78 | } 79 | 80 | [data-ty="input"]:before { 81 | content: '$'; 82 | } 83 | 84 | [data-ty][data-ty-prompt]:before { 85 | content: attr(data-ty-prompt); 86 | } 87 | 88 | [data-ty-cursor]:after { 89 | content: attr(data-ty-cursor); 90 | font-family: monospace; 91 | margin-left: 0.5em; 92 | -webkit-animation: blink 1s infinite; 93 | animation: blink 1s infinite; 94 | } 95 | 96 | 97 | /* Cursor animation */ 98 | 99 | @-webkit-keyframes blink { 100 | 50% { 101 | opacity: 0; 102 | } 103 | } 104 | 105 | @keyframes blink { 106 | 50% { 107 | opacity: 0; 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | hide: 3 | - navigation 4 | - toc 5 | 6 | title : GeoServerX 7 | description: modern python and CLI package for communicating with GeoServer 8 | --- 9 | # Welcome to geoserverx 10 | 11 | `geoserverx` is a modern Python package that provides an efficient and scalable way to interact with Geoserver REST APIs. It leverages the asynchronous capabilities of Python to offer a high-performance and reliable solution for managing Geoserver data and services. 12 | 13 | With geoserverx, users can easily access and modify data in Geoserver, such as uploading and deleting shapefiles, publishing layers, creating workspaces, styles, etc. . The package supports asynchronous requests along with synchronous method to the Geoserver REST API, which enables users to perform multiple tasks simultaneously, improving performance and reducing wait times. 14 | 15 | Apart from being implemented as Python package, geoserverx also provides CLI support for all of its operations. Which makes it useful for people who want to avoid Python all-together. 16 | 17 | Checkout official pypi link [here](https://pypi.org/project/geoserverx/) 18 | 19 | ## Get Started 20 | 21 | `geoserverx` can be installed using `pip` or `pip3` 22 | 23 |
24 | 25 | ```console 26 | 27 | pip install geoserverx 28 | 29 | ---> 100% 30 | ``` 31 |
32 | 33 | After which , It can be used in Python projects using sync, async methods or can ve used as Command Line tool 34 | 35 | ## Architecture 36 | 37 | `geoserverx` is built on top of `httpx` and `pydantic` libraries. It uses `httpx` for making HTTP requests and `pydantic` for data validation. The package is designed to be modular and extensible, allowing for easy integration with other libraries and frameworks. 38 | 39 | The package is structured into two main components: the `SyncGeoServerX` class and the `AsyncGeoServerX` class. The `SyncGeoServerX` class provides synchronous methods for interacting with Geoserver, while the `AsyncGeoServerX` class provides asynchronous methods using the `anyio` library 40 | 41 | ![layout](./assets/images/layout.png){ align=left } 42 | 43 | 44 | ## For testing purpose 45 | If you don't have GeoServer installed locally, feel free to use following command to quickly spin up Geoserver using [Docker](https://www.docker.com/) 46 | 47 |
48 | ```console 49 | docker run -e GEOSERVER_ADMIN_USER=admin -e GEOSERVER_ADMIN_PASSWORD=geoserver -e SAMPLE_DATA=true -p 8080:8080 kartoza/GeoServer 50 | ``` 51 |
52 | 53 | Please note that this will work on amd64 architecture machines. -------------------------------------------------------------------------------- /docs/pages/async/example.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | Here, we'll have a look at implementation `geoserverx` asynchronous Class 4 | 5 | !!! get "Get started" 6 | To start using `geoserverx` in Async mode, create a new instance of `AsyncGeoServerX` Class 7 | 8 | ## Setup Class instance 9 | 10 | `AsyncGeoServerX` Class has default username, password, url which points to default GeoServer settings. 11 | ```Python 12 | # Import class from package 13 | from geoserverx._async.gsx import AsyncGeoServerX 14 | import asyncio 15 | # Create class Instance with default paramaters 16 | client = AsyncGeoServerX() 17 | ``` 18 | We'll assume connection to local GeoServer with default credentials 19 | 20 | ## Get all workspaces 21 | 22 | ```Python hl_lines="7" 23 | from geoserverx._async.gsx import AsyncGeoServerX 24 | import asyncio 25 | 26 | async def get_info_raster_workspaces(url, username, password): 27 | print("-------------start-----------------") 28 | client = AsyncGeoServerX(username, password,url) 29 | print(await client.get_all_workspaces()) 30 | 31 | async def main(): 32 | await asyncio.gather(get_info_raster_workspaces(url='http://localhost:8080/geoserver/rest/',username='admin', password='GeoServer'),get_info_raster_workspaces(url='http://locahost:8080/geoserver/rest',username='admin', password='myP'),get_info_raster_workspaces(url='http://localhost:8080/geoserver/rest/',username='admin', password='GeoServer')) 33 | 34 | asyncio.run(main()) 35 | 36 | ''' Console 37 | -------------start----------------- 38 | -------------start----------------- 39 | -------------start----------------- 40 | workspaces=workspaceDict(workspace=[WorkspaceInBulk(name='MySimple', href='http://localhost:8080/geoserver/rest/workspaces/MySimple.json'), WorkspaceInBulk(name='MyHidden', href='http://localhost:8080/geoserver/rest/workspaces/MyHidden.json'), WorkspaceInBulk(name='MyDefault', href='http://localhost:8080/geoserver/rest/workspaces/MyDefault.json'), WorkspaceInBulk(name='nondefaultws', href='http://localhost:8080/geoserver/rest/workspaces/nondefaultws.json'), WorkspaceInBulk(name='mydefaultws', href='http://localhost:8080/geoserver/rest/workspaces/mydefaultws.json'), WorkspaceInBulk(name='ajadasfasdf', href='http://localhost:8080/geoserver/rest/workspaces/ajadasfasdf.json'), WorkspaceInBulk(name='ajada', href='http://localhost:8080/geoserver/rest/workspaces/ajada.json'), WorkspaceInBulk(name='aja', href='http://localhost:8080/geoserver/rest/workspaces/aja.json'), WorkspaceInBulk(name='cesium', href='http://localhost:8080/geoserver/rest/workspaces/cesium.json')]) 41 | ''' 42 | ``` 43 | 44 | ## Get Information about `cesium` workspace 45 | 46 | ```Python hl_lines="7" 47 | from geoserverx._async.gsx import AsyncGeoServerX 48 | import asyncio 49 | 50 | async def get_info_raster_workspaces(url, username, password,workspace): 51 | print("-------------start-----------------") 52 | client = AsyncGeoServerX(username, password,url) 53 | print(await client.get_workspace(workspace)) 54 | 55 | async def main(): 56 | await asyncio.gather(get_info_raster_workspaces(url='http://localhost:8080/geoserver/rest/',username='admin', password='GeoServer',workspace='cesium')) 57 | 58 | asyncio.run(main()) 59 | 60 | ''' Console 61 | -------------start----------------- 62 | workspace=SingleWorkspace(name='cesium', isolated=False, dateCreated='2023-02-13 06:43:28.793 UTC', dataStores='http://localhost:8080/geoserver/rest/workspaces/cesium/datastores.json', coverageStores='http://localhost:8080/geoserver/rest/workspaces/cesium/coveragestores.json', wmsStores='http://localhost:8080/geoserver/rest/workspaces/cesium/wmsstores.json', wmtsStores='http://localhost:8080/geoserver/rest/workspaces/cesium/wmtsstores.json') 63 | ''' 64 | ``` 65 | 66 | ## Create New workspaces 67 | 68 | * MyDefault - Default and not Isolated 69 | * MyHidden - Not Default and Isolated 70 | * MySimple - Not Default and not Isolated 71 | 72 | ```Python hl_lines="7" 73 | from geoserverx._async.gsx import AsyncGeoServerX 74 | import asyncio 75 | 76 | async def create_single_workspaces(url, username, password,workspace,default,isolated): 77 | print("-------------start-----------------") 78 | client = AsyncGeoServerX(username, password,url) 79 | print(await client.create_workspace(workspace, default,isolated)) 80 | 81 | async def main(): 82 | await asyncio.gather(create_single_workspaces( 83 | url='http://localhost:8080/geoserver/rest/',username='admin', password='GeoServer', 84 | workspace='AsyncMyDefault',default=True,isolated= False), 85 | create_single_workspaces( 86 | url='http://localhost:8080/geoserver/rest/',username='admin', password='GeoServer', 87 | workspace='AsyncMyHidden',default=False,isolated= True), 88 | create_single_workspaces( 89 | url='http://localhost:8080/geoserver/rest/',username='admin', password='GeoServer', 90 | workspace='AsyncMySimple',default=False,isolated= False)) 91 | 92 | asyncio.run(main()) 93 | 94 | ''' Console 95 | -------------start----------------- 96 | -------------start----------------- 97 | -------------start----------------- 98 | code=201 response='Data added successfully' 99 | code=201 response='Data added successfully' 100 | code=201 response='Data added successfully' 101 | ''' 102 | ``` 103 | 104 | ## Get all Vector stores in `cesium` workspace 105 | 106 | ```Python hl_lines="7" 107 | from geoserverx._async.gsx import AsyncGeoServerX 108 | import asyncio 109 | 110 | async def create_single_workspaces(url, username, password,workspace): 111 | print("-------------start-----------------") 112 | client = AsyncGeoServerX(username, password,url) 113 | print(await client.get_vector_stores_in_workspaces(workspace)) 114 | 115 | async def main(): 116 | await asyncio.gather(create_single_workspaces( 117 | url='http://localhost:8080/geoserver/rest/',username='admin', password='GeoServer', 118 | workspace='cesium')) 119 | 120 | asyncio.run(main()) 121 | 122 | ''' Console 123 | -------------start----------------- 124 | dataStores=DataStoreDict(dataStore=[DataStoreInBulk(name='mygpkgs', href='http://localhost:8080/geoserver/rest/workspaces/cesium/datastores/mygpkgs.json'), DataStoreInBulk(name='myshp', href='http://localhost:8080/geoserver/rest/workspaces/cesium/datastores/myshp.json'), DataStoreInBulk(name='mysql', href='http://localhost:8080/geoserver/rest/workspaces/cesium/datastores/mysql.json')]) 125 | ''' 126 | ``` 127 | 128 | ## Get Information of Vector store `myshp` in `cesium` workspace 129 | 130 | ```Python hl_lines="7" 131 | from geoserverx._async.gsx import AsyncGeoServerX 132 | import asyncio 133 | 134 | async def get_info_vector_workspaces(url, username, password,workspace,store): 135 | print("-------------start-----------------") 136 | client = AsyncGeoServerX(username, password,url) 137 | print(await client.get_vector_store(workspace,store)) 138 | 139 | async def main(): 140 | await asyncio.gather(get_info_vector_workspaces( 141 | url='http://localhost:8080/geoserver/rest/',username='admin', password='GeoServer', 142 | workspace='cesium',store='myshp')) 143 | 144 | asyncio.run(main()) 145 | 146 | 147 | ''' Console 148 | -------------start----------------- 149 | dataStore=DataStoreModelDetails(name='myshp', description=None, enabled=True, workspace=WorkspaceInBulk(name='cesium', href='http://localhost:8080/geoserver/rest/workspaces/cesium.json'), connectionParameters=EntryItem(entry=[DatastoreConnection(key='namespace', path='cesium'), DatastoreConnection(key='url', path='file:/path/to/nyc.shp')]), dateCreated='2023-02-28 18:14:01.199 UTC', dateModified=None, featureTypes='http://localhost:8080/geoserver/rest/workspaces/cesium/datastores/myshp/featuretypes.json') 150 | ''' 151 | ``` 152 | 177 | 178 | ## Get all Raster stores in `cesium` workspace 179 | 180 | ```Python hl_lines="7" 181 | from geoserverx._async.gsx import AsyncGeoServerX 182 | import asyncio 183 | 184 | async def get_all_raster_workspaces(url, username, password,workspace): 185 | print("-------------start-----------------") 186 | client = AsyncGeoServerX(username, password,url) 187 | print(await client.get_raster_stores_in_workspaces(workspace)) 188 | 189 | async def main(): 190 | await asyncio.gather(get_all_raster_workspaces( 191 | url='http://localhost:8080/geoserver/rest/',username='admin', password='GeoServer',workspace='cesium')) 192 | 193 | asyncio.run(main()) 194 | 195 | ''' Console 196 | -------------start----------------- 197 | coverageStores=CoveragesStoresDict(coverageStore=[CoveragesStoreInBulk(name='dem', href='http://localhost:8080/geoserver/rest/workspaces/cesium/coveragestores/dem.json'), CoveragesStoreInBulk(name='dsm', href='http://localhost:8080/geoserver/rest/workspaces/cesium/coveragestores/dsm.json'), CoveragesStoreInBulk(name='ortho', href='http://localhost:8080/geoserver/rest/workspaces/cesium/coveragestores/ortho.json')]) 198 | ''' 199 | ``` 200 | 201 | ## Get Information of Raster store `dsm` in `cesium` workspace 202 | 203 | ```Python hl_lines="7" 204 | from geoserverx._async.gsx import AsyncGeoServerX 205 | import asyncio 206 | 207 | async def get_info_raster_workspaces(url, username, password,workspace,store): 208 | print("-------------start-----------------") 209 | client = AsyncGeoServerX(username, password,url) 210 | print(await client.get_raster_store(workspace,store)) 211 | 212 | async def main(): 213 | await asyncio.gather(get_info_raster_workspaces( 214 | url='http://localhost:8080/geoserver/rest/',username='admin', password='GeoServer',workspace='cesium',store='dsm')) 215 | 216 | asyncio.run(main()) 217 | 218 | ''' Console 219 | -------------start----------------- 220 | coverageStore=CoveragesStoreModelDetail(name='dsm', description=None, enabled=True, workspace=WorkspaceInBulk(name='cesium', href='http://localhost:8080/geoserver/rest/workspaces/cesium.json'), url='file:///Users/krishnaglodha/Desktop/IGI_DATA/DSM/IGI_DSM1m1.tif', coverages='http://localhost:8080/geoserver/rest/workspaces/cesium/coveragestores/dsm/coverages.json', dateCreated='2023-02-23 13:39:48.417 UTC', metadata=None) 221 | ''' 222 | ``` 223 | 224 | ## Get all Styles in GeoServer 225 | 226 | ```Python hl_lines="7" 227 | from geoserverx._async.gsx import AsyncGeoServerX 228 | import asyncio 229 | 230 | async def get_info_raster_workspaces(url, username, password): 231 | print("-------------start-----------------") 232 | client = AsyncGeoServerX(username, password,url) 233 | print(await client.get_all_styles()) 234 | 235 | async def main(): 236 | await asyncio.gather(get_info_raster_workspaces( 237 | url='http://localhost:8080/geoserver/rest/',username='admin', password='GeoServer')) 238 | 239 | asyncio.run(main()) 240 | 241 | ''' Console 242 | -------------start----------------- 243 | styles=allStyle(style=[allStyleList(name='burg', href='http://localhost:8080/geoserver/rest/styles/burg.json'), allStyleList(name='capitals', href='http://localhost:8080/geoserver/rest/styles/capitals.json'), allStyleList(name='cite_lakes', href='http://localhost:8080/geoserver/rest/styles/cite_lakes.json'), allStyleList(name='dem', href='http://localhost:8080/geoserver/rest/styles/dem.json'), allStyleList(name='generic', href='http://localhost:8080/geoserver/rest/styles/generic.json'), allStyleList(name='giant_polygon', href='http://localhost:8080/geoserver/rest/styles/giant_polygon.json'), allStyleList(name='grass', href='http://localhost:8080/geoserver/rest/styles/grass.json'), allStyleList(name='green', href='http://localhost:8080/geoserver/rest/styles/green.json'), allStyleList(name='line', href='http://localhost:8080/geoserver/rest/styles/line.json'), allStyleList(name='poi', href='http://localhost:8080/geoserver/rest/styles/poi.json'), allStyleList(name='point', href='http://localhost:8080/geoserver/rest/styles/point.json'), allStyleList(name='poly_landmarks', href='http://localhost:8080/geoserver/rest/styles/poly_landmarks.json'), allStyleList(name='polygon', href='http://localhost:8080/geoserver/rest/styles/polygon.json'), allStyleList(name='pophatch', href='http://localhost:8080/geoserver/rest/styles/pophatch.json'), allStyleList(name='population', href='http://localhost:8080/geoserver/rest/styles/population.json'), allStyleList(name='rain', href='http://localhost:8080/geoserver/rest/styles/rain.json'), allStyleList(name='raster', href='http://localhost:8080/geoserver/rest/styles/raster.json'), allStyleList(name='restricted', href='http://localhost:8080/geoserver/rest/styles/restricted.json'), allStyleList(name='simple_roads', href='http://localhost:8080/geoserver/rest/styles/simple_roads.json'), allStyleList(name='simple_streams', href='http://localhost:8080/geoserver/rest/styles/simple_streams.json'), allStyleList(name='tiger_roads', href='http://localhost:8080/geoserver/rest/styles/tiger_roads.json')]) 244 | ''' 245 | ``` 246 | 247 | ## Get Single Style in GeoServer 248 | 249 | ```Python hl_lines="7" 250 | from geoserverx._async.gsx import AsyncGeoServerX 251 | import asyncio 252 | 253 | async def get_info_raster_workspaces(url, username, password,style): 254 | print("-------------start-----------------") 255 | client = AsyncGeoServerX(username, password,url) 256 | print(await client.get_style(style)) 257 | 258 | async def main(): 259 | await asyncio.gather(get_info_raster_workspaces( 260 | url='http://localhost:8080/geoserver/rest/',username='admin', password='GeoServer',style='poi')) 261 | 262 | asyncio.run(main()) 263 | ''' Console 264 | -------------start----------------- 265 | style=SingleStyle(name='poi', format='sld', languageVersion=langVersion(version='1.0.0'), filename='poi.sld') 266 | ''' 267 | ``` 268 | -------------------------------------------------------------------------------- /docs/pages/async/index.md: -------------------------------------------------------------------------------- 1 | # Asynchronous way of using geoserverx 2 | 3 | `geoserverx` allows user to call methods asynchronously. 4 | 5 | !!! get "Get started" 6 | To start using `geoserverx` in Sync mode, create a new instance of `AsyncGeoServerX` Class 7 | 8 | ## Setup Class instance 9 | 10 | `AsyncGeoServerX` Class has default username, password, url which points to default GeoServer settings. 11 | ```Python 12 | # Import class from package 13 | from geoserverx._async.gsx import AsyncGeoServerX 14 | # Create class Instance with default paramaters 15 | client = AsyncGeoServerX() 16 | ``` 17 | 18 | These paramaters however can be changed as follows 19 | ```Python 20 | # Import class from package 21 | from geoserverx._async.gsx import AsyncGeoServerX 22 | # Create class Instance with custom paramaters 23 | client = AsyncGeoServerX(username='mygeos', password='SecuredPass',url='http://127.0.0.1:9090/geoserver/rest/') 24 | ``` 25 | 26 | 27 | This class can also be used as context manager to manage the opening and closing connection automatically. 28 | ```Python 29 | # Import class from package 30 | from geoserverx._async.gsx import AsyncGeoServerX,GeoServerXAuth 31 | import asyncio 32 | # Create class Instance with custom paramaters 33 | client = AsyncGeoServerX(username='mygeos', password='SecuredPass',url='http://127.0.0.1:9090/geoserver/rest/') 34 | 35 | #Using with as 36 | async def main(): 37 | async with client as cl: 38 | response = await cl.get_all_workspaces() 39 | print(response) 40 | 41 | loop = asyncio.get_event_loop() 42 | loop.run_until_complete(main()) 43 | ``` -------------------------------------------------------------------------------- /docs/pages/async/raster-store.md: -------------------------------------------------------------------------------- 1 | # Raster Stores 2 | 3 | `geoserverx` allows users to access all/one raster stores from GeoServer 4 | 5 | 6 | ## Get all raster stores 7 | This command fetches all Vector store available in given workspace from GeoServer. 8 | 9 | ```py 10 | # Get all raster stores available in `cite` workspace 11 | await client.get_raster_stores_in_workspaces('cite') 12 | ``` 13 | 14 | 15 | ## Get single raster store 16 | 17 | This command fetches all Information about raster store available in given workspace from GeoServer. 18 | 19 | ```Python 20 | # Get all information about `image` raster stores available in `cite` workspace 21 | await client.get_raster_store(workspace='cite', store='image') 22 | ``` 23 | -------------------------------------------------------------------------------- /docs/pages/async/style.md: -------------------------------------------------------------------------------- 1 | # Style 2 | 3 | 4 | ## Get all Styles 5 | 6 | This command fetches all Styles available in GeoServer. 7 | 8 | ```Python 9 | # Get all styles available in GeoServer 10 | await client.get_all_styles() 11 | ``` 12 | 13 | 14 | ## Get single Style 15 | 16 | This command fetches information about particular Style from GeoServer. 17 | 18 | ```py 19 | # Get information about `population` style from GeoServer 20 | await client.get_style('population') 21 | ``` 22 | -------------------------------------------------------------------------------- /docs/pages/async/vector-store.md: -------------------------------------------------------------------------------- 1 | # Vector Stores 2 | 3 | `geoserverx` allows users to access all/one vector stores from GeoServer. As of now, `geoserverx` also supports new vector store creation for `shapefile` and `gpkg` data 4 | 5 | ## Get all Vector stores 6 | This command fetches all Vector store available in given workspace from GeoServer. 7 | 8 | ```Python 9 | # Get all vector stores available in `cite` workspace 10 | await client.get_vector_stores_in_workspaces('cite') 11 | ``` 12 | 13 | 14 | 15 | ## Get single Vector store 16 | This command fetches all Information about Vector store available in given workspace from GeoServer. 17 | 18 | ```Python 19 | # Get all information about `shape` vector stores available in `cite` workspace 20 | await client.get_vector_store(workspace='cite', store='shape') 21 | ``` 22 | 23 | 24 | ## Create new shapefile Vector store 25 | Use this command to create new Vector store based on `shapefile` path. 26 | 27 | ```Python 28 | # Create new store in `cite` workspace with name `shape` and using `path/for/shapefile` as local shapefile path 29 | await client.create_file_store(workspace='cite', store='shape', file='path/for/shapefile', service_type='shapefile') 30 | ``` 31 | 32 | 33 | ## Create new geopackage Vector store 34 | Use this command to create new Vector store based on `Geopackage` path. 35 | 36 | ```Python 37 | # Create new store in `cite` workspace with name `shape` and using `path/for/gpkg` as local Geopackage path 38 | await client.create_file_store(workspace='cite', store='shape', file='path/for/gpkg', service_type='gpkg') 39 | ``` 40 | 41 | 42 | ## Create new PostGIS Vector store 43 | Use this command to create new Vector store based on `PostGIS` connection. 44 | 45 | ```Python 46 | # Create new store in `cite` workspace with name `pg` and using `PostgreSQL` credentials 47 | await client.create_pg_store( 48 | name="pg", 49 | workspace="cite", 50 | host="localhost", 51 | port=5432, 52 | username="XXXXXXXX", 53 | password="XXXXXXXX", 54 | database="test") 55 | ``` 56 | 57 | ## Get all Vector layers 58 | This command fetches all Vector layers available in given workspace from GeoServer. 59 | 60 | ```Python 61 | # Get all vector layers available in `cite` workspace 62 | await client.get_all_layers(workspace='cite') 63 | ``` 64 | 65 | ## Get single Vector layer 66 | This command fetches all Information about Vector layer available in given workspace from GeoServer. 67 | 68 | ```Python 69 | # Get all information about `roads` vector layers available in `cite` workspace 70 | await client.get_vector_layer(workspace='cite', store='shape', layer='roads') 71 | ``` -------------------------------------------------------------------------------- /docs/pages/async/workspace.md: -------------------------------------------------------------------------------- 1 | # Workspaces 2 | 3 | `geoserverx` allows users to access all/one workspace from GeoServer, along with ability to add new workspaces. 4 | 5 | ## Get all workspaces 6 | This command fetches all workspaces available in GeoServer. No parameters are required to be passed. 7 | 8 | ```py 9 | # Get all workspaces in GeoServer 10 | await client.get_all_workspaces() 11 | ``` 12 | 13 | ## Get single workspace 14 | This command fetches workspace with paramter as name of it from GeoServer. 15 | ```Python 16 | # Get workspace with name `cite` 17 | await client.get_workspace('cite') 18 | ``` 19 | 20 | ## Create workspace 21 | This command allows user to create new workspace. 22 | Creating new workspace requires following parameters 23 | 24 | * Name `str` : To define Name of the workspace 25 | * default `bool` : To define whether to keep workspace as default or not 26 | * Isolated `bool` : To define whether to keep workspace Isolated or not 27 | 28 | ```Python 29 | #Create new workspace with name `my_wrkspc` , make it Default and Isolated 30 | await client.create_workspace(name='my_wrkspc',default=True,Isolated=True) 31 | ``` 32 | -------------------------------------------------------------------------------- /docs/pages/cli/example.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | Here, we'll have a look at implementation `geoserverx` synchronous Class. 4 | 5 | !!! get "Get started" 6 | To start using `gsx` CLI tool, install `geoserverx` package and turn on the environment. 7 | 8 | ## Setup CLI instance 9 | 10 |
11 | ```console 12 | $ pip install geoserverx 13 | ---> 100% 14 | 15 | $ gsx 16 | Usage: gsx [OPTIONS] COMMAND [ARGS]... 17 | Try 'gsx --help' for help. 18 | 19 | Error: Missing command. 20 | ``` 21 |
22 | 23 | We'll assume connection to local GeoServer with default credentials 24 | 25 | 26 | ## Get all workspaces 27 | 28 |
29 | ```console 30 | $ gsx workspaces 31 | 32 | {"workspaces": {"workspace": [{"name": "cesium", "href": 33 | "http://127.0.0.1:8080/geoserver/rest/workspaces/cesium.json"}]}} 34 | ``` 35 |
36 | 37 | 38 | ## Get single workspaces 39 | 40 |
41 | ```console 42 | $ gsx workspace --workspace cesium 43 | {"workspace": {"name": "cesium", "isolated": false, "dateCreated": "2023-02-13 44 | 06:43:28.793 UTC", "dataStores": 45 | "http://127.0.0.1:8080/geoserver/rest/workspaces/cesium/datastores.json", 46 | "coverageStores": 47 | "http://127.0.0.1:8080/geoserver/rest/workspaces/cesium/coveragestores.json", 48 | "wmsStores": "http://127.0.0.1:8080/geoserver/rest/workspaces/cesium/wmsstores.json", 49 | "wmtsStores": "http://127.0.0.1:8080/geoserver/rest/workspaces/cesium/wmtsstores.json"}} 50 | ``` 51 |
52 | 53 | 54 | ## Create single workspaces 55 | 56 |
57 | ```console 58 | $ gsx create-workspace --workspace mydefaultws --default 59 | code=201 response='Data added successfully' 60 | ``` 61 |
62 | 63 | ## Get all Vector stores 64 | 65 |
66 | ```console 67 | $ gsx vector-st-wp --workspace cesium 68 | {"dataStores": {"dataStore": [{"name": "mysqlllllll", "href": 69 | "http://127.0.0.1:8080/geoserver/rest/workspaces/cesium/datastores/mysqlllllll.json"}]}} 70 | ``` 71 |
72 | 73 | 74 | ## Get single Vector store information 75 | 76 |
77 | ```console 78 | $ gsx vector-store --workspace cesium --store mysqlllllll 79 | {"dataStore": {"name": "mysqlllllll", "description": null, "enabled": true, "workspace": 80 | {"name": "cesium", "href": 81 | "http://127.0.0.1:8080/geoserver/rest/workspaces/cesium.json"}, "connectionParameters": 82 | {"entry": [{"key": "Evictor run periodicity", "path": "300"}, {"key": "fetch size", 83 | "path": "1000"}, {"key": "Expose primary keys", "path": "false"}, {"key": "validate 84 | connections", "path": "true"}, {"key": "Connection timeout", "path": "20"}, {"key": 85 | "Batch insert size", "path": "1"}, {"key": "database", "path": "appsolicitous_dcra"}, 86 | {"key": "port", "path": "3306"}, {"key": "passwd", "path": 87 | "crypt1:njsGJk9CEY8jiaqfSYyQGZeB9RLB2sh7"}, {"key": "storage engine", "path": "MyISAM"}, 88 | {"key": "min connections", "path": "1"}, {"key": "dbtype", "path": "mysql"}, {"key": 89 | "host", "path": "23.29.118.44"}, {"key": "namespace", "path": "cesium"}, {"key": "max 90 | connections", "path": "10"}, {"key": "Evictor tests per run", "path": "3"}, {"key": "Test 91 | while idle", "path": "true"}, {"key": "user", "path": "appsolicitous_dcra"}, {"key": "Max 92 | connection idle time", "path": "300"}]}, "dateCreated": "2023-02-28 10:38:52.70 UTC", 93 | "dateModified": null, "featureTypes": 94 | "http://127.0.0.1:8080/geoserver/rest/workspaces/cesium/datastores/mysqlllllll/featuretyp 95 | es.json"}} 96 | 97 | ``` 98 |
99 | 100 | 101 | ## Get all raster stores 102 | 103 |
104 | ```console 105 | $ gsx raster-st-wp --workspace cesium 106 | {"coverageStores": {"coverageStore": [{"name": "dem", "href": 107 | "http://127.0.0.1:8080/geoserver/rest/workspaces/cesium/coveragestores/dem.json"}, 108 | {"name": "dsm", "href": 109 | "http://127.0.0.1:8080/geoserver/rest/workspaces/cesium/coveragestores/dsm.json"}, 110 | {"name": "ortho", "href": 111 | "http://127.0.0.1:8080/geoserver/rest/workspaces/cesium/coveragestores/ortho.json"}]}} 112 | ``` 113 |
114 | 115 | 116 | ## Get single raster store information 117 | 118 |
119 | ```console 120 | $ gsx raster-store --workspace cesium --store dsm 121 | {"coverageStore": {"name": "dsm", "description": null, "enabled": true, "workspace": 122 | {"name": "cesium", "href": 123 | "http://127.0.0.1:8080/geoserver/rest/workspaces/cesium.json"}, "url": 124 | "file:///Users/krishnaglodha/Desktop/IGI_DATA/DSM/IGI_DSM1m1.tif", "coverages": 125 | "http://127.0.0.1:8080/geoserver/rest/workspaces/cesium/coveragestores/dsm/coverages.json 126 | ", "dateCreated": "2023-02-23 13:39:48.417 UTC", "metadata": null}} 127 | 128 | ``` 129 |
130 | 131 | 132 | ## Get all styles 133 | 134 |
135 | ```console 136 | $ gsx styles 137 | {"styles": {"style": [{"name": "burg", "href": 138 | "http://127.0.0.1:8080/geoserver/rest/styles/burg.json"}, {"name": "capitals", "href": 139 | "http://127.0.0.1:8080/geoserver/rest/styles/capitals.json"}, {"name": "cite_lakes", 140 | "href": "http://127.0.0.1:8080/geoserver/rest/styles/cite_lakes.json"}, {"name": "dem", 141 | "href": "http://127.0.0.1:8080/geoserver/rest/styles/dem.json"}, {"name": "generic", 142 | "href": "http://127.0.0.1:8080/geoserver/rest/styles/generic.json"}, {"name": 143 | "giant_polygon", "href": 144 | "http://127.0.0.1:8080/geoserver/rest/styles/giant_polygon.json"}, {"name": "grass", 145 | "href": "http://127.0.0.1:8080/geoserver/rest/styles/grass.json"}, {"name": "green", 146 | "href": "http://127.0.0.1:8080/geoserver/rest/styles/green.json"}, {"name": "line", 147 | "href": "http://127.0.0.1:8080/geoserver/rest/styles/line.json"}]}} 148 | ``` 149 |
150 | 151 | ## Get single style information 152 | 153 |
154 | ```console 155 | $ gsx style --style p 156 | oi 157 | {"style": {"name": "poi", "format": "sld", "languageVersion": {"version": "1.0.0"}, 158 | "filename": "poi.sld"}} 159 | ``` 160 |
161 | 162 | -------------------------------------------------------------------------------- /docs/pages/cli/index.md: -------------------------------------------------------------------------------- 1 | # Command line access 2 | 3 | `geoserverx` allows users to leverage power of command line to communicate with GeoServer. 4 | `gsx` is a command line tool by `geoserverx`. 5 | 6 | ## Installation 7 | To use `gsx` , Install `geoserverx` using `pip` on local environment. 8 | 9 |
10 | 11 | ```console 12 | 13 | pip install geoserverx 14 | 15 | ---> 100% 16 | 17 | $ gsx --help 18 | Usage: gsx [OPTIONS] COMMAND [ARGS]... 19 | 20 | GeoserverX CLI tools to talk to Geoserver efficiently . 21 | 22 | Options: 23 | --install-completion [bash|zsh|fish|powershell|pwsh] 24 | Install completion for the specified shell. 25 | --show-completion [bash|zsh|fish|powershell|pwsh] 26 | Show completion for the specified shell, to 27 | copy it or customize the installation. 28 | --help Show this message and exit. 29 | 30 | Commands: 31 | create-file Create Vector Layer in Geoserver 32 | create-workspace Add workspace in the Geoserver 33 | raster-st-wp Get raster stores in specific workspaces 34 | raster-store Get raster store information in specific workspaces 35 | style Get style in Geoserver 36 | styles Get all styles in Geoserver 37 | vector-st-wp Get vector stores in specific workspaces 38 | vector-store Get vector store information in specific workspaces 39 | workspace Get workspace in the Geoserver 40 | workspaces Get all workspaces in the Geoserver 41 | ``` 42 |
43 | 44 | Checkout other pages to understand how to use Command line -------------------------------------------------------------------------------- /docs/pages/cli/raster-store.md: -------------------------------------------------------------------------------- 1 | # Raster Stores 2 | 3 | `geoserverx` allows users to access all/one raster stores from GeoServer. As of now, `geoserverx` also supports new raster store creation for `shapefile` and `gpkg` data 4 | 5 | !!! get "Get started" 6 | To start using `geoserverx` using command line, activate the Environment where package is installed and use `gsx` command 7 | 8 | 9 | ## Paramters for all raster stores in Workspace command 10 | 11 |
12 | ```console 13 | $ gsx raster-st-wp --help 14 | Usage: gsx raster-st-wp [OPTIONS] 15 | 16 | Get raster stores in specific workspaces 17 | 18 | Options: 19 | --request [sync|async] [default: requestEnum._sync] 20 | --workspace TEXT Workspace name [required] 21 | --url TEXT Geoserver REST URL [default: 22 | http://127.0.0.1:8080/geoserver/rest/] 23 | --password TEXT Geoserver Password [default: geoserver] 24 | --username TEXT Geoserver username [default: admin] 25 | --help Show this message and exit. 26 | ``` 27 |
28 | 29 | As listed above, `raster-st-wp` command accepts following parameters. 30 | 31 | * request type ( sync or async ) 32 | * url - Geoserver REST URL 33 | * password - Password for GeoServer 34 | * username - Username for GeoServer 35 | 36 | All these parameters have default value setup which will work for local default installation. Apart from this `workspace` paramters must be added which aims at the workspace we are interested in 37 | 38 | ## Get all raster stores 39 | 40 |
41 | ```console 42 | $ gsx raster-st-wp --workspace cesium 43 | {"coverageStores": {"coverageStore": [{"name": "dem", "href": 44 | "http://127.0.0.1:8080/geoserver/rest/workspaces/cesium/coveragestores/dem.json"}, 45 | {"name": "dsm", "href": 46 | "http://127.0.0.1:8080/geoserver/rest/workspaces/cesium/coveragestores/dsm.json"}, 47 | {"name": "ortho", "href": 48 | "http://127.0.0.1:8080/geoserver/rest/workspaces/cesium/coveragestores/ortho.json"}]}} 49 | ``` 50 |
51 | 52 | ## Paramters for single raster stores command 53 | 54 |
55 | ```console 56 | $ gsx raster-store --help 57 | Usage: gsx raster-store [OPTIONS] 58 | 59 | Get raster store information in specific workspaces 60 | 61 | Options: 62 | --request [sync|async] [default: requestEnum._sync] 63 | --workspace TEXT Workspace name [required] 64 | --store TEXT Store name [required] 65 | --url TEXT Geoserver REST URL [default: 66 | http://127.0.0.1:8080/geoserver/rest/] 67 | --password TEXT Geoserver Password [default: GeoServer] 68 | --username TEXT Geoserver username [default: admin] 69 | --help Show this message and exit. 70 | 71 | ``` 72 |
73 | 74 | This command takes an additional parameter of name of the store. 75 | 76 | ## Get single raster store information 77 | 78 |
79 | ```console 80 | $ gsx raster-store --workspace cesium --store dsm 81 | {"coverageStore": {"name": "dsm", "description": null, "enabled": true, "workspace": 82 | {"name": "cesium", "href": 83 | "http://127.0.0.1:8080/geoserver/rest/workspaces/cesium.json"}, "url": 84 | "file:///Users/krishnaglodha/Desktop/IGI_DATA/DSM/IGI_DSM1m1.tif", "coverages": 85 | "http://127.0.0.1:8080/geoserver/rest/workspaces/cesium/coveragestores/dsm/coverages.json 86 | ", "dateCreated": "2023-02-23 13:39:48.417 UTC", "metadata": null}} 87 | 88 | ``` 89 |
90 | -------------------------------------------------------------------------------- /docs/pages/cli/style.md: -------------------------------------------------------------------------------- 1 | # Style 2 | 3 | `geoserverx` allows users to access all/one raster stores from GeoServer. As of now, `geoserverx` also supports new raster store creation for `shapefile` and `gpkg` data 4 | 5 | !!! get "Get started" 6 | To start using `geoserverx` using command line, activate the Environment where package is installed and use `gsx` command 7 | 8 | 9 | ## Paramters for all styles command 10 | 11 |
12 | ```console 13 | $ gsx styles --help 14 | Usage: gsx styles [OPTIONS] 15 | 16 | Get all styles in Geoserver 17 | 18 | Options: 19 | --request [sync|async] [default: requestEnum._sync] 20 | --url TEXT Geoserver REST URL [default: 21 | http://127.0.0.1:8080/geoserver/rest/] 22 | --password TEXT Geoserver Password [default: GeoServer] 23 | --username TEXT Geoserver username [default: admin] 24 | --help Show this message and exit. 25 | ``` 26 |
27 | 28 | As listed above, `styles` command accepts following parameters. 29 | 30 | * request type ( sync or async ) 31 | * url - Geoserver REST URL 32 | * password - Password for GeoServer 33 | * username - Username for GeoServer 34 | 35 | All these parameters have default value setup which will work for local default installation. 36 | 37 | ## Get all styles 38 | 39 |
40 | ```console 41 | $ gsx styles 42 | {"styles": {"style": [{"name": "burg", "href": 43 | "http://127.0.0.1:8080/geoserver/rest/styles/burg.json"}, {"name": "capitals", "href": 44 | "http://127.0.0.1:8080/geoserver/rest/styles/capitals.json"}, {"name": "cite_lakes", 45 | "href": "http://127.0.0.1:8080/geoserver/rest/styles/cite_lakes.json"}, {"name": "dem", 46 | "href": "http://127.0.0.1:8080/geoserver/rest/styles/dem.json"}, {"name": "generic", 47 | "href": "http://127.0.0.1:8080/geoserver/rest/styles/generic.json"}, {"name": 48 | "giant_polygon", "href": 49 | "http://127.0.0.1:8080/geoserver/rest/styles/giant_polygon.json"}, {"name": "grass", 50 | "href": "http://127.0.0.1:8080/geoserver/rest/styles/grass.json"}, {"name": "green", 51 | "href": "http://127.0.0.1:8080/geoserver/rest/styles/green.json"}, {"name": "line", 52 | "href": "http://127.0.0.1:8080/geoserver/rest/styles/line.json"}]}} 53 | ``` 54 |
55 | 56 | ## Paramters for single style command 57 | 58 |
59 | ```console 60 | $ gsx style --help 61 | Usage: gsx style [OPTIONS] 62 | 63 | Get style in Geoserver 64 | 65 | Options: 66 | --request [sync|async] [default: requestEnum._sync] 67 | --url TEXT Geoserver REST URL [default: 68 | http://127.0.0.1:8080/geoserver/rest/] 69 | --style TEXT Style name [required] 70 | --password TEXT Geoserver Password [default: GeoServer] 71 | --username TEXT Geoserver username [default: admin] 72 | --help Show this message and exit. 73 | 74 | ``` 75 |
76 | 77 | This command takes an additional parameter of name of the style. 78 | 79 | ## Get single style information 80 | 81 |
82 | ```console 83 | $ gsx style --style p 84 | oi 85 | {"style": {"name": "poi", "format": "sld", "languageVersion": {"version": "1.0.0"}, 86 | "filename": "poi.sld"}} 87 | ``` 88 |
89 | 90 | -------------------------------------------------------------------------------- /docs/pages/cli/vector-store.md: -------------------------------------------------------------------------------- 1 | # Vector Stores 2 | 3 | `geoserverx` allows users to access all/one vector stores from GeoServer. As of now, `geoserverx` also supports new vector store creation for `shapefile` and `gpkg` data 4 | 5 | !!! get "Get started" 6 | To start using `geoserverx` using command line, activate the Environment where package is installed and use `gsx` command 7 | 8 | 9 | ## Paramters for all Vector stores in Workspace command 10 | 11 |
12 | ```console 13 | $ gsx vector-st-wp --help 14 | Usage: gsx vector-st-wp [OPTIONS] 15 | 16 | Get vector stores in specific workspaces 17 | Options: 18 | --request [sync|async] [default: requestEnum._sync] 19 | --workspace TEXT Workspace name [required] 20 | --url TEXT Geoserver REST URL [default: 21 | http://127.0.0.1:8080/geoserver/rest/] 22 | --password TEXT Geoserver Password [default: GeoServer] 23 | --username TEXT Geoserver username [default: admin] 24 | --help Show this message and exit. 25 | ``` 26 |
27 | 28 | As listed above, `vector-st-wp` command accepts following parameters. 29 | 30 | * request type ( sync or async ) 31 | * url - Geoserver REST URL 32 | * password - Password for GeoServer 33 | * username - Username for GeoServer 34 | 35 | All these parameters have default value setup which will work for local default installation. Apart from this `workspace` paramters must be added which aims at the workspace we are interested in 36 | 37 | ## Get all Vector stores 38 | 39 |
40 | ```console 41 | $ gsx vector-st-wp --workspace cesium 42 | {"dataStores": {"dataStore": [{"name": "mysqlllllll", "href": 43 | "http://127.0.0.1:8080/geoserver/rest/workspaces/cesium/datastores/mysqlllllll.json"}]}} 44 | ``` 45 |
46 | 47 | ## Paramters for single Vector stores command 48 | 49 |
50 | ```console 51 | $ gsx vector-store --help 52 | Usage: gsx vector-store [OPTIONS] 53 | 54 | Get vector store information in specific workspaces 55 | 56 | Options: 57 | --request [sync|async] [default: requestEnum._sync] 58 | --workspace TEXT Workspace name [required] 59 | --store TEXT Store name [required] 60 | --url TEXT Geoserver REST URL [default: 61 | http://127.0.0.1:8080/geoserver/rest/] 62 | --password TEXT Geoserver Password [default: GeoServer] 63 | --username TEXT Geoserver username [default: admin] 64 | --help Show this message and exit. 65 | 66 | ``` 67 |
68 | 69 | This command takes an additional parameter of name of the store. 70 | 71 | ## Get single Vector store information 72 | 73 |
74 | ```console 75 | $ gsx vector-store --workspace cesium --store mysqlllllll 76 | {"dataStore": {"name": "mysqlllllll", "description": null, "enabled": true, "workspace": 77 | {"name": "cesium", "href": 78 | "http://127.0.0.1:8080/geoserver/rest/workspaces/cesium.json"}, "connectionParameters": 79 | {"entry": [{"key": "Evictor run periodicity", "path": "300"}, {"key": "fetch size", 80 | "path": "1000"}, {"key": "Expose primary keys", "path": "false"}, {"key": "validate 81 | connections", "path": "true"}, {"key": "Connection timeout", "path": "20"}, {"key": 82 | "Batch insert size", "path": "1"}, {"key": "database", "path": "appsolicitous_dcra"}, 83 | {"key": "port", "path": "3306"}, {"key": "passwd", "path": 84 | "crypt1:njsGJk9CEY8jiaqfSYyQGZeB9RLB2sh7"}, {"key": "storage engine", "path": "MyISAM"}, 85 | {"key": "min connections", "path": "1"}, {"key": "dbtype", "path": "mysql"}, {"key": 86 | "host", "path": "23.29.118.44"}, {"key": "namespace", "path": "cesium"}, {"key": "max 87 | connections", "path": "10"}, {"key": "Evictor tests per run", "path": "3"}, {"key": "Test 88 | while idle", "path": "true"}, {"key": "user", "path": "appsolicitous_dcra"}, {"key": "Max 89 | connection idle time", "path": "300"}]}, "dateCreated": "2023-02-28 10:38:52.70 UTC", 90 | "dateModified": null, "featureTypes": 91 | "http://127.0.0.1:8080/geoserver/rest/workspaces/cesium/datastores/mysqlllllll/featuretyp 92 | es.json"}} 93 | 94 | ``` 95 |
96 | -------------------------------------------------------------------------------- /docs/pages/cli/workspace.md: -------------------------------------------------------------------------------- 1 | # Workspaces 2 | 3 | `geoserverx` allows users to access all/one workspace from GeoServer, along with ability to add new workspaces. 4 | 5 | !!! get "Get started" 6 | To start using `geoserverx` using command line, activate the Environment where package is installed and use `gsx` command 7 | 8 | 9 | 10 | ## Paramters for all workspaces command 11 | 12 |
13 | 14 | ```console 15 | $ gsx workspaces --help 16 | 17 | Usage: gsx workspaces [OPTIONS] 18 | 19 | Get all workspaces in the Geoserver 20 | 21 | Options: 22 | --request [sync|async] [default: requestEnum._sync] 23 | --url TEXT Geoserver REST URL [default: 24 | http://127.0.0.1:8080/geoserver/rest/] 25 | --password TEXT Geoserver Password [default: GeoServer] 26 | --username TEXT Geoserver username [default: admin] 27 | --help Show this message and exit. 28 | ``` 29 |
30 | 31 | As listed above, `workspaces` command accepts four parameters. 32 | 33 | * request type ( sync or async ) 34 | * url - Geoserver REST URL 35 | * password - Password for GeoServer 36 | * username - Username for GeoServer 37 | 38 | All these parameters have default value setup which will work for local default installation 39 | 40 | ## Get all workspaces 41 | 42 |
43 | ```console 44 | $ gsx workspaces 45 | 46 | {"workspaces": {"workspace": [{"name": "cesium", "href": 47 | "http://127.0.0.1:8080/geoserver/rest/workspaces/cesium.json"}]}} 48 | ``` 49 |
50 | 51 | ## Get all workspaces of hosted GeoServer 52 | 53 |
54 | ```console 55 | $ gsx workspaces --url http://locahost:8080/geoserver/rest --password myPassword --username admin 56 | {"workspaces": {"workspace": [{"name": "giz", "href": 57 | "http://locahost:8080/geoserver/rest/workspaces/giz.json"}]}} 58 | ``` 59 |
60 | 61 | 62 | ## Paramters to get single workspace command 63 | 64 |
65 | 66 | ```console 67 | $ gsx workspace --help 68 | Usage: gsx workspace [OPTIONS] 69 | 70 | Get workspace in the Geoserver 71 | 72 | Options: 73 | --request [sync|async] [default: requestEnum._sync] 74 | --workspace TEXT Workspace name [required] 75 | --url TEXT Geoserver REST URL [default: 76 | http://127.0.0.1:8080/geoserver/rest/] 77 | --password TEXT Geoserver Password [default: GeoServer] 78 | --username TEXT Geoserver username [default: admin] 79 | --help Show this message and exit. 80 | ``` 81 |
82 | 83 | As listed above, `workspace` accepts `workspace` parameter as the name of workspace 84 | 85 | 86 | ## Get single workspaces 87 | 88 |
89 | ```console 90 | $ gsx workspace --workspace cesium 91 | {"workspace": {"name": "cesium", "isolated": false, "dateCreated": "2023-02-13 92 | 06:43:28.793 UTC", "dataStores": 93 | "http://127.0.0.1:8080/geoserver/rest/workspaces/cesium/datastores.json", 94 | "coverageStores": 95 | "http://127.0.0.1:8080/geoserver/rest/workspaces/cesium/coveragestores.json", 96 | "wmsStores": "http://127.0.0.1:8080/geoserver/rest/workspaces/cesium/wmsstores.json", 97 | "wmtsStores": "http://127.0.0.1:8080/geoserver/rest/workspaces/cesium/wmtsstores.json"}} 98 | ``` 99 |
100 | 101 | 102 | ## Paramters for create workspace command 103 | 104 |
105 | 106 | ```console 107 | $ gsx create-workspace --help 108 | Usage: gsx create-workspace [OPTIONS] 109 | 110 | Add workspace in the Geoserver 111 | 112 | Options: 113 | --request [sync|async] [default: requestEnum._sync] 114 | --workspace TEXT Workspace name [required] 115 | --default / --no-default Make workspace default? [default: no-default] 116 | --isolated / --no-isolated Make workspace isolated? [default: no-isolated] 117 | --url TEXT Geoserver REST URL [default: 118 | http://127.0.0.1:8080/geoserver/rest/] 119 | --password TEXT Geoserver Password [default: GeoServer] 120 | --username TEXT Geoserver username [default: admin] 121 | --help Show this message and exit. 122 | ``` 123 |
124 | 125 | As listed above, `create-workspace` command accepts parameters as follows 126 | 127 | * workspace - name of workspace 128 | * --default/--no-default - To keep workspace either default or not 129 | * --isolated/--no-isolated - To keep workspace either isolated or not 130 | 131 | 132 | ## Create single workspaces 133 | 134 |
135 | ```console 136 | $ gsx create-workspace --workspace mydefaultws --default 137 | code=201 response='Data added successfully' 138 | ``` 139 |
-------------------------------------------------------------------------------- /docs/pages/sync/example.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | Here, we'll have a look at implementation `geoserverx` synchronous Class 4 | 5 | !!! get "Get started" 6 | To start using `geoserverx` in Sync mode, create a new instance of `SyncGeoServerX` Class 7 | 8 | ## Setup Class instance 9 | 10 | `SyncGeoServerX` Class has default username, password, url which points to default GeoServer settings. 11 | ```Python 12 | # Import class from package 13 | from geoserverx._sync.gsx import SyncGeoServerX 14 | # Create class Instance with default paramaters 15 | client = SyncGeoServerX() 16 | ``` 17 | We'll assume connection to local GeoServer with default credentials 18 | 19 | ## Get all workspaces 20 | 21 | ```Python hl_lines="8" linenums="1" 22 | # Import Class from Package 23 | from geoserverx._sync.gsx import SyncGeoServerX 24 | 25 | def get_all_gs_workspaces(url, username, password): 26 | print("-------------start-----------------") 27 | # Setup Class Instance 28 | client = SyncGeoServerX(username, password,url) 29 | return client.get_all_workspaces() 30 | 31 | result = get_all_gs_workspaces(url='http://localhost:8080/geoserver/rest/',username='admin', password='GeoServer') 32 | print(result.json()) 33 | ''' Console 34 | -------------start----------------- 35 | {"workspaces": {"workspace": [{"name": "nondefaultws", "href": "http://localhost:8080/geoserver/rest/workspaces/nondefaultws.json"}, 36 | {"name": "mydefaultws", "href": "http://localhost:8080/geoserver/rest/workspaces/mydefaultws.json"}, 37 | {"name": "ajadasfasdf", "href": "http://localhost:8080/geoserver/rest/workspaces/ajadasfasdf.json"}, 38 | {"name": "ajada", "href": "http://localhost:8080/geoserver/rest/workspaces/ajada.json"}, 39 | {"name": "aja", "href": "http://localhost:8080/geoserver/rest/workspaces/aja.json"}, {"name": "cesium", "href": "http://localhost:8080/geoserver/rest/workspaces/cesium.json"}]}} 40 | ''' 41 | ``` 42 | 43 | ## Get Information about `cesium` workspace 44 | 45 | ```Python hl_lines="7" 46 | # Import Class from Package 47 | from geoserverx._sync.gsx import SyncGeoServerX 48 | 49 | def get_single_workspaces(url, username, password,workspace): 50 | print("-------------start-----------------") 51 | # Setup Class Instance 52 | client = SyncGeoServerX(username, password,url) 53 | return client.get_workspace(workspace) 54 | 55 | result = get_single_workspaces(url='http://localhost:8080/geoserver/rest/',username='admin', password='GeoServer',workspace='cesium') 56 | 57 | print(result.json()) 58 | ''' Console 59 | -------------start----------------- 60 | {"workspace": {"name": "cesium", "isolated": false, "dateCreated": "2023-02-13 06:43:28.793 UTC", "dataStores": "http://localhost:8080/geoserver/rest/workspaces/cesium/datastores.json", "coverageStores": "http://localhost:8080/geoserver/rest/workspaces/cesium/coveragestores.json", "wmsStores": "http://localhost:8080/geoserver/rest/workspaces/cesium/wmsstores.json", "wmtsStores": "http://localhost:8080/geoserver/rest/workspaces/cesium/wmtsstores.json"}} 61 | ''' 62 | ``` 63 | 64 | ## Create New workspaces 65 | 66 | * MyDefault - Default and not Isolated 67 | * MyHidden - Not Default and Isolated 68 | * MySimple - Not Default and not Isolated 69 | 70 | ```Python hl_lines="11 12 14 15 17 18" 71 | 72 | # Import Class from Package 73 | from geoserverx._sync.gsx import SyncGeoServerX 74 | 75 | 76 | def create_single_workspaces(url, username, password,workspace,default,isolated): 77 | print("-------------start-----------------") 78 | # Setup Class Instance 79 | client = SyncGeoServerX(username, password,url) 80 | return client.create_workspace(workspace, default,isolated) 81 | 82 | first = create_single_workspaces(url='http://localhost:8080/geoserver/rest/',username='admin', password='GeoServer', 83 | workspace='MyDefault',default=True,isolated= False) 84 | print(first.json()) 85 | second = create_single_workspaces(url='http://localhost:8080/geoserver/rest/',username='admin', password='GeoServer', 86 | workspace='MyHidden',default=False,isolated= True) 87 | print(second.json()) 88 | third = create_single_workspaces(url='http://localhost:8080/geoserver/rest/',username='admin', password='GeoServer', 89 | workspace='MySimple',default=False,isolated= False) 90 | print(third.json()) 91 | ''' Console 92 | -------------start----------------- 93 | -------------start----------------- 94 | -------------start----------------- 95 | code=201 response='Data added successfully' 96 | code=201 response='Data added successfully' 97 | code=201 response='Data added successfully' 98 | ''' 99 | ``` 100 | ![workspace created](/assets/images/workspace_created.png "workspace created") 101 | 102 | ## Get all Vector stores in `cesium` workspace 103 | 104 | ```Python hl_lines="8" 105 | 106 | # Import Class from Package 107 | from geoserverx._sync.gsx import SyncGeoServerX 108 | 109 | def get_all_vector_workspaces(url, username, password,workspace): 110 | print("-------------start-----------------") 111 | # Setup Class Instance 112 | client = SyncGeoServerX(username, password,url) 113 | return client.get_vector_stores_in_workspaces(workspace) 114 | 115 | result = get_vector_store(url='http://localhost:8080/geoserver/rest/',username='admin', password='GeoServer', 116 | workspace='cesium') 117 | print(result.json()) 118 | 119 | ''' Console 120 | -------------start----------------- 121 | {"dataStores": {"dataStore": [{"name": "mysqlllllll", "href": "http://localhost:8080/geoserver/rest/workspaces/cesium/datastores/mysqlllllll.json"}]}} 122 | ''' 123 | ``` 124 | 125 | ## Get Information of Vector store `mysqldb` in `cesium` workspace 126 | 127 | ```Python hl_lines="8" 128 | 129 | # Import Class from Package 130 | from geoserverx._sync.gsx import SyncGeoServerX 131 | 132 | def get_info_vector_workspaces(url, username, password,workspace,store): 133 | print("-------------start-----------------") 134 | # Setup Class Instance 135 | client = SyncGeoServerX(username, password,url) 136 | return client.get_vector_store(workspace,store) 137 | 138 | result = get_info_vector_workspaces(url='http://localhost:8080/geoserver/rest/',username='admin', password='GeoServer', 139 | workspace='cesium',store='mysqldb' ) 140 | print(result.json()) 141 | 142 | ''' Console 143 | -------------start----------------- 144 | {"dataStore": {"name": "mysqldb", "description": null, "enabled": true, 145 | "workspace": {"name": "cesium", "href": "http://localhost:8080/geoserver/rest/workspaces/cesium.json"}, 146 | "connectionParameters": {"entry": [{"key": "Evictor run periodicity", "path": "300"}, {"key": "fetch size", "path": "1000"}, {"key": "Expose primary keys", "path": "false"}, {"key": "validate connections", "path": "true"}, {"key": "Connection timeout", "path": "20"}, {"key": "Batch insert size", "path": "1"}, {"key": "database", "path": "appsolicitous_dcra"}, {"key": "port", "path": "3306"}, {"key": "passwd", "path": "crypt1:jxnPgWTsBoUAVin1wtCLWgIqmZ4DSEWx"}, {"key": "storage engine", "path": "MyISAM"}, {"key": "min connections", "path": "1"}, {"key": "dbtype", "path": "mysql"}, {"key": "host", "path": "23.29.118.44"}, {"key": "namespace", "path": "cesium"}, {"key": "max connections", "path": "10"}, {"key": "Evictor tests per run", "path": "3"}, {"key": "Test while idle", "path": "true"}, {"key": "user", "path": "appsolicitous_dcra"}, {"key": "Max connection idle time", "path": "300"}]}, "dateCreated": "2023-02-28 10:38:52.70 UTC", "dateModified": null, 147 | "featureTypes": "http://localhost:8080/geoserver/rest/workspaces/cesium/datastores/mysqlllllll/featuretypes.json"}} 148 | ''' 149 | ``` 150 | 151 | ## Create new shapefile Vector store in `cesium` workspace with name `natural_earth` 152 | 153 | Create new store using Shapefile available at given path 154 | ```Python hl_lines="8" 155 | from geoserverx._sync.gsx import SyncGeoServerX 156 | 157 | def add_vector_workspaces(url, username, password,workspace,store,file): 158 | print("-------------start-----------------") 159 | # Setup Class Instance 160 | client = SyncGeoServerX(username, password,url) 161 | return client.create_file_store(workspace, store, file, service_type='shapefile') 162 | 163 | result = add_vector_workspaces(url='http://localhost:8080/geoserver/rest/',username='admin', password='GeoServer', 164 | workspace='cesium',store='natural_earth', file='/Users/krishnaglodha/Downloads/ne_10m_populated_places_simple/ne_10m_populated_places_simple.shp' ) 165 | print(result.json()) 166 | ''' Console 167 | -------------start----------------- 168 | {"code": 201, "response": "Data added successfully"} 169 | ''' 170 | ``` 171 | 172 | ![new_shp_vector](/assets/images/new_shp_vector.png "new_shp_vector") 173 | 174 | ## Get all Raster stores in `cesium` workspace 175 | 176 | ```Python hl_lines="8" 177 | # Import Class from Package 178 | from geoserverx._sync.gsx import SyncGeoServerX 179 | 180 | def get_all_raster_workspaces(url, username, password,workspace): 181 | print("-------------start-----------------") 182 | # Setup Class Instance 183 | client = SyncGeoServerX(username, password,url) 184 | return client.get_raster_stores_in_workspaces(workspace) 185 | 186 | result = get_all_raster_workspaces(url='http://localhost:8080/geoserver/rest/',username='admin', password='GeoServer', 187 | workspace='cesium') 188 | print(result.json()) 189 | 190 | ''' Console 191 | -------------start----------------- 192 | {"coverageStores": {"coverageStore": [{"name": "dem", "href": "http://localhost:8080/geoserver/rest/workspaces/cesium/coveragestores/dem.json"}, {"name": "dsm", "href": "http://localhost:8080/geoserver/rest/workspaces/cesium/coveragestores/dsm.json"}, {"name": "ortho", "href": "http://localhost:8080/geoserver/rest/workspaces/cesium/coveragestores/ortho.json"}]}} 193 | ''' 194 | ``` 195 | 196 | ## Get Information of Raster store `dsm` in `cesium` workspace 197 | 198 | ```Python hl_lines="8" 199 | # Import Class from Package 200 | from geoserverx._sync.gsx import SyncGeoServerX 201 | 202 | def get_info_raster_workspaces(url, username, password,workspace,store): 203 | print("-------------start-----------------") 204 | # Setup Class Instance 205 | client = SyncGeoServerX(username, password,url) 206 | return client.get_raster_store(workspace,store) 207 | 208 | result = get_info_raster_workspaces(url='http://localhost:8080/geoserver/rest/',username='admin', password='GeoServer', 209 | workspace='cesium',store='dsm' ) 210 | print(result.json()) 211 | 212 | ''' Console 213 | -------------start----------------- 214 | {"coverageStore": {"name": "dsm", "description": null, "enabled": true, "workspace": {"name": "cesium", "href": "http://localhost:8080/geoserver/rest/workspaces/cesium.json"}, "url": "file:///Users/krishnaglodha/Desktop/IGI_DATA/DSM/IGI_DSM1m1.tif", "coverages": "http://localhost:8080/geoserver/rest/workspaces/cesium/coveragestores/dsm/coverages.json", "dateCreated": "2023-02-23 13:39:48.417 UTC", "metadata": null}} 215 | ''' 216 | ``` 217 | 218 | ## Get all Styles in GeoServer 219 | 220 | ```Python hl_lines="8" 221 | # Import Class from Package 222 | from geoserverx._sync.gsx import SyncGeoServerX 223 | 224 | def get_all_styles(url, username, password): 225 | print("-------------start-----------------") 226 | 227 | client = SyncGeoServerX(username, password,url) 228 | return client.get_all_styles() 229 | 230 | result = get_all_styles(url='http://localhost:8080/geoserver/rest/',username='admin', password='GeoServer' ) 231 | print(result.json()) 232 | 233 | ''' Console 234 | -------------start----------------- 235 | {"styles": {"style": [{"name": "burg", "href": "http://localhost:8080/geoserver/rest/styles/burg.json"}, {"name": "capitals", "href": "http://localhost:8080/geoserver/rest/styles/capitals.json"}, {"name": "cite_lakes", "href": "http://localhost:8080/geoserver/rest/styles/cite_lakes.json"}, {"name": "dem", "href": "http://localhost:8080/geoserver/rest/styles/dem.json"}, {"name": "generic", "href": "http://localhost:8080/geoserver/rest/styles/generic.json"}, {"name": "giant_polygon", "href": "http://localhost:8080/geoserver/rest/styles/giant_polygon.json"}, {"name": "grass", "href": "http://localhost:8080/geoserver/rest/styles/grass.json"}, {"name": "green", "href": "http://localhost:8080/geoserver/rest/styles/green.json"}, {"name": "line", "href": "http://localhost:8080/geoserver/rest/styles/line.json"}, {"name": "poi", "href": "http://localhost:8080/geoserver/rest/styles/poi.json"}, {"name": "point", "href": "http://localhost:8080/geoserver/rest/styles/point.json"}, {"name": "poly_landmarks", "href": "http://localhost:8080/geoserver/rest/styles/poly_landmarks.json"}, {"name": "polygon", "href": "http://localhost:8080/geoserver/rest/styles/polygon.json"}, {"name": "pophatch", "href": "http://localhost:8080/geoserver/rest/styles/pophatch.json"}, {"name": "population", "href": "http://localhost:8080/geoserver/rest/styles/population.json"}, {"name": "rain", "href": "http://localhost:8080/geoserver/rest/styles/rain.json"}, {"name": "raster", "href": "http://localhost:8080/geoserver/rest/styles/raster.json"}, {"name": "restricted", "href": "http://localhost:8080/geoserver/rest/styles/restricted.json"}, {"name": "simple_roads", "href": "http://localhost:8080/geoserver/rest/styles/simple_roads.json"}, {"name": "simple_streams", "href": "http://localhost:8080/geoserver/rest/styles/simple_streams.json"}, {"name": "tiger_roads", "href": "http://localhost:8080/geoserver/rest/styles/tiger_roads.json"}]}} 236 | ''' 237 | ``` 238 | 239 | ## Get Single Style in GeoServer 240 | 241 | ```Python hl_lines="8" 242 | # Import Class from Package 243 | from geoserverx._sync.gsx import SyncGeoServerX 244 | 245 | def get_style_info(url, username, password,style): 246 | print("-------------start-----------------") 247 | 248 | client = SyncGeoServerX(username, password,url) 249 | return client.get_style(style) 250 | 251 | result = get_style_info(url='http://localhost:8080/geoserver/rest/',username='admin', password='GeoServer',style='poi' ) 252 | print(result.json()) 253 | ''' Console 254 | -------------start----------------- 255 | {"style": {"name": "poi", "format": "sld", "languageVersion": {"version": "1.0.0"}, "filename": "poi.sld"}} 256 | ''' 257 | ``` 258 | -------------------------------------------------------------------------------- /docs/pages/sync/index.md: -------------------------------------------------------------------------------- 1 | # Synchronous way of using geoserverx 2 | 3 | `geoserverx` allows user to call methods synchronously. 4 | 5 | !!! get "Get started" 6 | To start using `geoserverx` in Sync mode, create a new instance of `SyncGeoServerX` Class 7 | 8 | ## Setup Class instance 9 | 10 | `SyncGeoServerX` Class has default username, password, url which points to default GeoServer settings. 11 | ```Python 12 | # Import class from package 13 | from geoserverx._sync.gsx import SyncGeoServerX 14 | # Create class Instance with default paramaters 15 | client = SyncGeoServerX() 16 | ``` 17 | 18 | These paramaters however can be changed as follows 19 | ```Python 20 | # Import class from package 21 | from geoserverx._sync.gsx import SyncGeoServerX 22 | # Create class Instance with custom paramaters 23 | client = SyncGeoServerX(username='mygeos', password='SecuredPass',url='http://127.0.0.1:9090/geoserver/rest/') 24 | ``` 25 | 26 | This class can also be used as context manager to manage the opening and closing connection automatically. 27 | ```Python 28 | # Import class from package 29 | from geoserverx._sync.gsx import SyncGeoServerX 30 | # Create class Instance with custom paramaters 31 | client = SyncGeoServerX(username='mygeos', password='SecuredPass',url='http://127.0.0.1:9090/geoserver/rest/') 32 | 33 | #Using with as 34 | with client as cl : 35 | response = cl.get_all_workspaces() 36 | ``` -------------------------------------------------------------------------------- /docs/pages/sync/raster-store.md: -------------------------------------------------------------------------------- 1 | # Raster Stores 2 | 3 | `geoserverx` allows users to access all/one raster stores from GeoServer 4 | 5 | 6 | ## Get all raster stores 7 | This command fetches all Vector store available in given workspace from GeoServer. 8 | 9 | ```py 10 | # Get all raster stores available in `cite` workspace 11 | client.get_raster_stores_in_workspaces('cite') 12 | ``` 13 | 14 | 15 | ## Get single raster store 16 | 17 | This command fetches all Information about raster store available in given workspace from GeoServer. 18 | 19 | ```Python 20 | # Get all information about `image` raster stores available in `cite` workspace 21 | 22 | client.get_raster_store(workspace='cite', store='image') 23 | ``` 24 | -------------------------------------------------------------------------------- /docs/pages/sync/style.md: -------------------------------------------------------------------------------- 1 | # Style 2 | 3 | 4 | ## Get all Styles 5 | 6 | This command fetches all Styles available in GeoServer. 7 | 8 | ```Python 9 | # Get all styles available in GeoServer 10 | client.get_all_styles() 11 | ``` 12 | 13 | 14 | ## Get single Style 15 | 16 | This command fetches information about particular Style from GeoServer. 17 | 18 | ```py 19 | # Get information about `population` style from GeoServer 20 | client.get_style('population') 21 | ``` 22 | -------------------------------------------------------------------------------- /docs/pages/sync/vector-store.md: -------------------------------------------------------------------------------- 1 | # Vector Stores 2 | 3 | `geoserverx` allows users to access all/one vector stores from GeoServer. As of now, `geoserverx` also supports new vector store creation for `shapefile` and `gpkg` data 4 | 5 | ## Get all Vector stores 6 | This command fetches all Vector store available in given workspace from GeoServer. 7 | 8 | ```Python 9 | # Get all vector stores available in `cite` workspace 10 | client.get_vector_stores_in_workspaces('cite') 11 | ``` 12 | 13 | 14 | 15 | ## Get single Vector store 16 | This command fetches all Information about Vector store available in given workspace from GeoServer. 17 | 18 | ```Python 19 | # Get all information about `shape` vector stores available in `cite` workspace 20 | 21 | client.get_vector_store(workspace='cite', store='shape') 22 | ``` 23 | 24 | 25 | ## Create new shapefile Vector store 26 | Use this command to create new Vector store based on `shapefile` path. 27 | 28 | ```Python 29 | # Create new store in `cite` workspace with name `shape` and using `path/for/shapefile` as local shapefile path 30 | client.create_file_store(workspace='cite', store='shape', file='path/for/shapefile', service_type='shapefile') 31 | ``` 32 | 33 | 34 | ## Create new geopackage Vector store 35 | Use this command to create new Vector store based on `Geopackage` path. 36 | 37 | ```Python 38 | # Create new store in `cite` workspace with name `shape` and using `path/for/gpkg` as local Geopackage path 39 | client.create_file_store(workspace='cite', store='shape', file='path/for/gpkg', service_type='gpkg') 40 | ``` 41 | 42 | ## Create new PostGIS Vector store 43 | Use this command to create new Vector store based on `PostGIS` connection. 44 | 45 | ```Python 46 | # Create new store in `cite` workspace with name `pg` and using `PostgreSQL` credentials 47 | client.create_pg_store( 48 | name="pg", 49 | workspace="cite", 50 | host="localhost", 51 | port=5432, 52 | username="XXXXXXXX", 53 | password="XXXXXXXX", 54 | database="test") 55 | ``` 56 | 57 | ## Get all Vector layers 58 | This command fetches all Vector layers available in given workspace from GeoServer. 59 | 60 | ```Python 61 | # Get all vector layers available in `cite` workspace 62 | client.get_all_layers(workspace='cite') 63 | ``` 64 | 65 | ## Get single Vector layer 66 | This command fetches all Information about Vector layer available in given workspace from GeoServer. 67 | 68 | ```Python 69 | # Get all information about `roads` vector layers available in `cite` workspace 70 | client.get_vector_layer(workspace='cite', store='shape', layer='roads') 71 | ``` 72 | -------------------------------------------------------------------------------- /docs/pages/sync/workspace.md: -------------------------------------------------------------------------------- 1 | # Workspaces 2 | 3 | `geoserverx` allows users to access all/one workspace from GeoServer, along with ability to add new workspaces. 4 | 5 | ## Get all workspaces 6 | This command fetches all workspaces available in GeoServer. No paramters are required to be passed. 7 | 8 | ```Python 9 | # Get all workspaces in GeoServer 10 | client.get_all_workspaces() 11 | ``` 12 | 13 | ## Get single workspace 14 | This command fetches workspace with paramter as name of it from GeoServer. 15 | ```Python 16 | # Get workspace with name `cite` 17 | client.get_workspace('cite') 18 | ``` 19 | 20 | ## Create workspace 21 | This command allows user to create new workspace. 22 | Creating new workspace requires following parameters 23 | 24 | * Name `str` : To define Name of the workspace 25 | * default `bool` : To define whether to keep workspace as default or not 26 | * Isolated `bool` : To define whether to keep workspace Isolated or not 27 | 28 | ```Python 29 | #Create new workspace with name `my_wrkspc` , make it Default and Isolated 30 | client.create_workspace(name='my_wrkspc',default=True,Isolated=True) 31 | ``` 32 | -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: geoserverx 2 | site_description: "geoserverx is a modern python and CLI package for communicating with Geoserver." 3 | site_url: https://geobeyond.github.io/geoserverx/ 4 | site_author: Geobeyond 5 | copyright: "© 2024 Geobeyond" 6 | theme: 7 | name: material 8 | icon: 9 | logo: material/package-variant 10 | favicon: material/package-variant 11 | palette: 12 | primary: white 13 | features: 14 | - content.code.copy 15 | - navigation.tabs 16 | - navigation.tabs.sticky 17 | repo_url: https://github.com/geobeyond/geoserverx 18 | plugins: 19 | - social 20 | - search 21 | nav: 22 | - Home: index.md 23 | - Sync : 24 | - pages/sync/index.md 25 | - Workspaces : pages/sync/workspace.md 26 | - Vector Data : pages/sync/vector-store.md 27 | - Raster Data : pages/sync/raster-store.md 28 | - Style : pages/sync/style.md 29 | - Example : pages/sync/example.md 30 | - Async : 31 | - pages/async/index.md 32 | - Workspaces : pages/async/workspace.md 33 | - Vector Data : pages/async/vector-store.md 34 | - Raster Data : pages/async/raster-store.md 35 | - Style : pages/async/style.md 36 | - Example : pages/async/example.md 37 | - Command Line : 38 | - pages/cli/index.md 39 | - Workspaces : pages/cli/workspace.md 40 | - Vector Data : pages/cli/vector-store.md 41 | - Raster Data : pages/cli/raster-store.md 42 | - Style : pages/cli/style.md 43 | - Example : pages/cli/example.md 44 | markdown_extensions: 45 | - meta # option to add some meta tags on top, title, author, date, etc 46 | - admonition # adds the note, question, tip boxes, eg: !!! tip "my tip" 47 | - pymdownx.details # advanced collapsible panels 48 | - pymdownx.superfences # advanced features; such as line number, flow chart, python shell 49 | - footnotes # notes bottom of page 50 | - attr_list # used to size images 51 | - md_in_html # used to size images 52 | - pymdownx.tabbed: 53 | alternate_style: true 54 | 55 | extra_css: 56 | # pygeoapi primary color with light and dark variations from material.io 57 | # https://material.io/resources/color/#!/?view.left=0&view.right=1 58 | - assets/stylesheets/termynal.css 59 | - assets/stylesheets/custom.css 60 | 61 | extra_javascript: 62 | - assets/javascripts/termynal.js 63 | - assets/javascripts/custom.js 64 | 65 | extra: 66 | generator: false -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "geoserverx" 3 | version = "0.0.4" 4 | description = "geoserverx is a modern python and CLI package for communicating with Geoserver." 5 | authors = ["krishnaglodha "] 6 | readme = "README.md" 7 | 8 | [tool.poetry.scripts] 9 | gsx = "geoserverx.cli.cli:app" 10 | 11 | [tool.poetry.dependencies] 12 | python = "^3.9" 13 | httpx = "^0.24.1" 14 | pydantic = "2.8.2" 15 | typer = "^0.4.1" 16 | rich = "^12.5.1" 17 | 18 | [tool.poetry.group.dev.dependencies] 19 | pytest = "^7.1.2" 20 | respx = "^0.20.1" 21 | mypy = "*" 22 | ruff = "^0.6.3" 23 | black = "^24.8.0" 24 | isort = "^5.10.1" 25 | pytest-asyncio = "^0.21.0" 26 | anyio = {extras = ["trio"], version = "^3.3.4"} 27 | 28 | [tool.poetry.group.docs] 29 | optional = true 30 | [tool.poetry.group.docs.dependencies] 31 | mkdocs-material = "^9.5.0" 32 | pillow = "*" 33 | CairoSVG= "*" 34 | 35 | 36 | [build-system] 37 | requires = ["poetry-core>=1.0.0"] 38 | build-backend = "poetry.core.masonry.api" 39 | -------------------------------------------------------------------------------- /social/2be55c97396bb6811061ba3194e30e4d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geobeyond/geoserverx/64349483b643d4c662da9d358286728360a38015/social/2be55c97396bb6811061ba3194e30e4d.png -------------------------------------------------------------------------------- /social/53d44762e0d6016d6de24215dfdd6676.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geobeyond/geoserverx/64349483b643d4c662da9d358286728360a38015/social/53d44762e0d6016d6de24215dfdd6676.png -------------------------------------------------------------------------------- /social/548c1d117ad531ea470f4c6f675062a4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geobeyond/geoserverx/64349483b643d4c662da9d358286728360a38015/social/548c1d117ad531ea470f4c6f675062a4.png -------------------------------------------------------------------------------- /social/92f5fd10f5766422436223bc15a7c8ef.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geobeyond/geoserverx/64349483b643d4c662da9d358286728360a38015/social/92f5fd10f5766422436223bc15a7c8ef.png -------------------------------------------------------------------------------- /social/9eef2d77b3d71b7533499b2f651f63d4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geobeyond/geoserverx/64349483b643d4c662da9d358286728360a38015/social/9eef2d77b3d71b7533499b2f651f63d4.png -------------------------------------------------------------------------------- /social/a05d671ab8d4f043eadc8e9f00b2feec.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geobeyond/geoserverx/64349483b643d4c662da9d358286728360a38015/social/a05d671ab8d4f043eadc8e9f00b2feec.png -------------------------------------------------------------------------------- /social/ab5486e06e38b9b0518f6930cc6a8248.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geobeyond/geoserverx/64349483b643d4c662da9d358286728360a38015/social/ab5486e06e38b9b0518f6930cc6a8248.png -------------------------------------------------------------------------------- /social/df41e99874754a66cab3b04028b0eb6b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geobeyond/geoserverx/64349483b643d4c662da9d358286728360a38015/social/df41e99874754a66cab3b04028b0eb6b.png -------------------------------------------------------------------------------- /social/f510d45f3b407da60e907251443fcf59.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geobeyond/geoserverx/64349483b643d4c662da9d358286728360a38015/social/f510d45f3b407da60e907251443fcf59.png -------------------------------------------------------------------------------- /social/fonts/Roboto/Black Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geobeyond/geoserverx/64349483b643d4c662da9d358286728360a38015/social/fonts/Roboto/Black Italic.ttf -------------------------------------------------------------------------------- /social/fonts/Roboto/Black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geobeyond/geoserverx/64349483b643d4c662da9d358286728360a38015/social/fonts/Roboto/Black.ttf -------------------------------------------------------------------------------- /social/fonts/Roboto/Bold Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geobeyond/geoserverx/64349483b643d4c662da9d358286728360a38015/social/fonts/Roboto/Bold Italic.ttf -------------------------------------------------------------------------------- /social/fonts/Roboto/Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geobeyond/geoserverx/64349483b643d4c662da9d358286728360a38015/social/fonts/Roboto/Bold.ttf -------------------------------------------------------------------------------- /social/fonts/Roboto/Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geobeyond/geoserverx/64349483b643d4c662da9d358286728360a38015/social/fonts/Roboto/Italic.ttf -------------------------------------------------------------------------------- /social/fonts/Roboto/Light Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geobeyond/geoserverx/64349483b643d4c662da9d358286728360a38015/social/fonts/Roboto/Light Italic.ttf -------------------------------------------------------------------------------- /social/fonts/Roboto/Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geobeyond/geoserverx/64349483b643d4c662da9d358286728360a38015/social/fonts/Roboto/Light.ttf -------------------------------------------------------------------------------- /social/fonts/Roboto/Medium Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geobeyond/geoserverx/64349483b643d4c662da9d358286728360a38015/social/fonts/Roboto/Medium Italic.ttf -------------------------------------------------------------------------------- /social/fonts/Roboto/Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geobeyond/geoserverx/64349483b643d4c662da9d358286728360a38015/social/fonts/Roboto/Medium.ttf -------------------------------------------------------------------------------- /social/fonts/Roboto/Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geobeyond/geoserverx/64349483b643d4c662da9d358286728360a38015/social/fonts/Roboto/Regular.ttf -------------------------------------------------------------------------------- /social/fonts/Roboto/Thin Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geobeyond/geoserverx/64349483b643d4c662da9d358286728360a38015/social/fonts/Roboto/Thin Italic.ttf -------------------------------------------------------------------------------- /social/fonts/Roboto/Thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geobeyond/geoserverx/64349483b643d4c662da9d358286728360a38015/social/fonts/Roboto/Thin.ttf -------------------------------------------------------------------------------- /src/geoserverx/__init__.py: -------------------------------------------------------------------------------- 1 | from . import _async, _sync, models, utils 2 | 3 | __version__ = "0.1.0" 4 | __author__ = "krishnaglodha " 5 | __all__ = [_sync, _async, utils, models] 6 | -------------------------------------------------------------------------------- /src/geoserverx/_async/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geobeyond/geoserverx/64349483b643d4c662da9d358286728360a38015/src/geoserverx/_async/__init__.py -------------------------------------------------------------------------------- /src/geoserverx/_async/gsx.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import Optional, Union 3 | 4 | import httpx 5 | 6 | from geoserverx.models.coverages_store import CoveragesStoreModel, CoveragesStoresModel 7 | from geoserverx.models.data_store import ( 8 | CreateDataStoreModel, 9 | CreateStoreItem, 10 | DataStoreModel, 11 | DataStoresModel, 12 | MainCreateDataStoreModel, 13 | ) 14 | from geoserverx.models.geofence import NewRule, Rule, RulesResponse 15 | from geoserverx.models.gs_response import GSResponse 16 | from geoserverx.models.layer_group import LayerGroupsModel 17 | from geoserverx.models.layers import LayerModel, LayersModel 18 | from geoserverx.models.style import AllStylesModel, StyleModel 19 | from geoserverx.models.workspace import ( 20 | NewWorkspace, 21 | NewWorkspaceInfo, 22 | WorkspaceModel, 23 | WorkspacesModel, 24 | ) 25 | from geoserverx.utils.auth import GeoServerXAuth 26 | from geoserverx.utils.custom_exceptions import GSModuleNotFound 27 | from geoserverx.utils.enums import GSResponseEnum 28 | from geoserverx.utils.errors import GeoServerXError 29 | from geoserverx.utils.http_client import AsyncClient 30 | from geoserverx.utils.logger import std_out_logger 31 | from geoserverx.utils.services.async_datastore import ( 32 | AddDataStoreProtocol, 33 | CreateFileStore, 34 | GPKGfileStore, 35 | ShapefileStore, 36 | ) 37 | 38 | 39 | @dataclass 40 | class AsyncGeoServerX: 41 | """ 42 | Async Geoserver client 43 | """ 44 | 45 | username: str = "admin" 46 | password: str = "geoserver" 47 | url: str = "http://127.0.0.1:8080/geoserver/rest/" 48 | head = {"Content-Type": "application/json"} 49 | 50 | def __post_init__(self): 51 | if not self.username and not self.password and not self.url: 52 | raise GeoServerXError(0, "Username, Password and URL is missing") 53 | elif not self.username or self.username == "": 54 | raise GeoServerXError(0, "Username is missing") 55 | elif not self.password or self.password == "": 56 | raise GeoServerXError(0, "password is missing") 57 | elif not self.url or self.url == "": 58 | raise GeoServerXError(0, "URL is missing") 59 | self.http_client = AsyncClient( 60 | base_url=self.url, 61 | auth=(self.username, self.password), 62 | ) 63 | 64 | async def __aenter__(self) -> "AsyncGeoServerX": 65 | return self 66 | 67 | async def __aexit__(self, exc_t, exc_v, exc_tb) -> None: 68 | await self.close() 69 | 70 | async def close(self) -> None: 71 | await self.http_client.aclose() 72 | 73 | @staticmethod 74 | def from_auth( 75 | auth: GeoServerXAuth, 76 | ) -> "AsyncGeoServerX": 77 | return AsyncGeoServerX(auth.username, auth.password, auth.url) 78 | 79 | def response_recognise(self, r) -> GSResponse: 80 | if r == 401: 81 | resp = GSResponseEnum._401.value 82 | elif r == 500: 83 | resp = GSResponseEnum._500.value 84 | elif r == 503: 85 | resp = GSResponseEnum._503.value 86 | elif r == 404: 87 | resp = GSResponseEnum._404.value 88 | elif r == 403: 89 | resp = GSResponseEnum._403.value 90 | elif r == 201: 91 | resp = GSResponseEnum._201.value 92 | elif r == 200: 93 | resp = GSResponseEnum._200.value 94 | elif r == 409: 95 | resp = GSResponseEnum._409.value 96 | return GSResponse.model_validate(resp) 97 | 98 | # check if certain module/plugin exists in geoserver 99 | async def check_modules(self, name) -> Union[bool, GSResponse]: 100 | Client = self.http_client 101 | try: 102 | response = await Client.get("about/status.json") 103 | response.raise_for_status() # Raises an HTTPError for bad responses (4xx and 5xx) 104 | 105 | # Extract and check the modules 106 | modules = [ 107 | item["name"].lower() for item in response.json()["statuss"]["status"] 108 | ] 109 | if name.lower() in modules: 110 | return True 111 | else: 112 | # Raise exception if the plugin is not found 113 | raise GSModuleNotFound(f"'{name}' plugin not found") 114 | 115 | except httpx.HTTPStatusError as e: 116 | # Handle HTTP errors (e.g., 4xx, 5xx) 117 | return self.response_recognise(e.response.status_code) 118 | except httpx.RequestError as e: 119 | # Handle other request errors (e.g., network problems) 120 | return self.response_recognise(e.response.status_code) 121 | except GSModuleNotFound as e: 122 | # Handle Module not found exception 123 | return GSResponse(code=412, response=str(e)) 124 | 125 | # Get all workspaces 126 | async def get_all_workspaces(self) -> Union[WorkspacesModel, GSResponse]: 127 | Client = self.http_client 128 | responses = await Client.get("workspaces") 129 | if responses.status_code == 200: 130 | return WorkspacesModel.model_validate(responses.json()) 131 | else: 132 | results = self.response_recognise(responses.status_code) 133 | return results 134 | 135 | # Get specific workspaces 136 | async def get_workspace(self, workspace: str) -> Union[WorkspaceModel, GSResponse]: 137 | Client = self.http_client 138 | responses = await Client.get(f"workspaces/{workspace}") 139 | if responses.status_code == 200: 140 | return WorkspaceModel.model_validate(responses.json()) 141 | else: 142 | results = self.response_recognise(responses.status_code) 143 | return results 144 | 145 | # Create workspace 146 | async def create_workspace( 147 | self, name: str, default: bool = False, Isolated: bool = False 148 | ) -> GSResponse: 149 | Client = self.http_client 150 | payload: NewWorkspace = NewWorkspace( 151 | workspace=NewWorkspaceInfo(name=name, isolated=Isolated) 152 | ) 153 | responses = await Client.post( 154 | f"workspaces?default={default}", 155 | data=payload.model_dump_json(), 156 | headers=self.head, 157 | ) 158 | results = self.response_recognise(responses.status_code) 159 | return results 160 | 161 | # Get vector stores in specific workspaces 162 | async def get_vector_stores_in_workspaces(self, workspace: str) -> DataStoresModel: 163 | Client = self.http_client 164 | responses = await Client.get(f"workspaces/{workspace}/datastores") 165 | if responses.status_code == 200: 166 | return DataStoresModel.model_validate(responses.json()) 167 | else: 168 | results = self.response_recognise(responses.status_code) 169 | return results 170 | 171 | # Get raster stores in specific workspaces 172 | async def get_raster_stores_in_workspaces( 173 | self, workspace: str 174 | ) -> CoveragesStoresModel: 175 | Client = self.http_client 176 | responses = await Client.get(f"workspaces/{workspace}/coveragestores") 177 | if responses.status_code == 200: 178 | return CoveragesStoresModel.model_validate(responses.json()) 179 | else: 180 | results = self.response_recognise(responses.status_code) 181 | return results 182 | 183 | # Get vector store information in specific workspaces 184 | async def get_vector_store(self, workspace: str, store: str) -> DataStoreModel: 185 | url = f"workspaces/{workspace}/datastores/{store}.json" 186 | Client = self.http_client 187 | responses = await Client.get(url) 188 | if responses.status_code == 200: 189 | return DataStoreModel.model_validate(responses.json()) 190 | else: 191 | results = self.response_recognise(responses.status_code) 192 | return results 193 | 194 | # Get raster store information in specific workspaces 195 | async def get_raster_store(self, workspace: str, store: str) -> CoveragesStoreModel: 196 | url = f"workspaces/{workspace}/coveragestores/{store}.json" 197 | Client = self.http_client 198 | responses = await Client.get(url) 199 | if responses.status_code == 200: 200 | return CoveragesStoreModel.model_validate(responses.json()) 201 | else: 202 | results = self.response_recognise(responses.status_code) 203 | return results 204 | 205 | # Get all styles in GS 206 | async def get_all_styles(self) -> AllStylesModel: 207 | Client = self.http_client 208 | responses = await Client.get("styles") 209 | if responses.status_code == 200: 210 | return AllStylesModel.model_validate(responses.json()) 211 | else: 212 | results = self.response_recognise(responses.status_code) 213 | return results 214 | 215 | # Get specific style in GS 216 | async def get_style(self, style: str) -> StyleModel: 217 | Client = self.http_client 218 | responses = await Client.get(f"styles/{style}.json") 219 | if responses.status_code == 200: 220 | return StyleModel.model_validate(responses.json()) 221 | else: 222 | results = self.response_recognise(responses.status_code) 223 | return results 224 | 225 | # Add postgres db 226 | async def create_pg_store( 227 | self, 228 | name: str, 229 | workspace: str, 230 | host: str, 231 | port: int, 232 | username: str, 233 | password: str, 234 | database: str, 235 | ) -> GSResponse: 236 | payload = MainCreateDataStoreModel( 237 | dataStore=CreateDataStoreModel( 238 | name=name, 239 | connectionParameters=CreateStoreItem( 240 | host=host, 241 | port=port, 242 | database=database, 243 | user=username, 244 | passwd=password, 245 | dbtype="postgis", 246 | ).model_dump(exclude_none=True), 247 | ) 248 | ) 249 | Client = self.http_client 250 | responses = await Client.post( 251 | f"workspaces/{workspace}/datastores/", 252 | data=payload.model_dump_json(), 253 | headers=self.head, 254 | ) 255 | results = self.response_recognise(responses.status_code) 256 | return results 257 | 258 | async def create_file_store(self, workspace: str, store: str, file, service_type): 259 | service: AddDataStoreProtocol = CreateFileStore() 260 | 261 | if service_type == "shapefile": 262 | service = ShapefileStore( 263 | client=self.http_client, 264 | service=service, 265 | logger=std_out_logger("Shapefile"), 266 | file=file, 267 | ) 268 | elif service_type == "gpkg": 269 | service = GPKGfileStore( 270 | client=self.http_client, 271 | service=service, 272 | logger=std_out_logger("GeoPackage"), 273 | file=file, 274 | ) 275 | else: 276 | raise ValueError(f"Service type {service_type} not supported") 277 | responses = await service.addFile(self.http_client, workspace, store) 278 | return self.response_recognise(responses) 279 | 280 | if service_type == "shapefile": 281 | service = ShapefileStore( 282 | client=self.http_client, 283 | service=service, 284 | logger=std_out_logger("Shapefile"), 285 | file=file, 286 | ) 287 | elif service_type == "gpkg": 288 | service = GPKGfileStore( 289 | service=service, logger=std_out_logger("GeoPackage"), file=file 290 | ) 291 | else: 292 | raise ValueError(f"Service type {service_type} not supported") 293 | await service.addFile(self.http_client, workspace, store) 294 | 295 | # Get all layers 296 | async def get_all_layers( 297 | self, workspace: Optional[str] = None 298 | ) -> Union[LayersModel, GSResponse]: 299 | Client = self.http_client 300 | if workspace: 301 | responses = await Client.get(f"/workspaces/{workspace}/layers") 302 | else: 303 | responses = await Client.get("layers") 304 | if responses.status_code == 200: 305 | return LayersModel.model_validate(responses.json()) 306 | else: 307 | results = self.response_recognise(responses.status_code) 308 | return results 309 | 310 | # Get specific layer 311 | async def get_layer( 312 | self, workspace: str, layer: str 313 | ) -> Union[LayerModel, GSResponse]: 314 | Client = self.http_client 315 | responses = await Client.get(f"layers/{workspace}:{layer}") 316 | if responses.status_code == 200: 317 | return LayerModel.model_validate(responses.json()) 318 | else: 319 | results = self.response_recognise(responses.status_code) 320 | return results 321 | 322 | # Delete specific layer 323 | async def delete_layer(self, workspace: str, layer: str) -> GSResponse: 324 | Client = self.http_client 325 | responses = await Client.delete(f"layers/{workspace}:{layer}") 326 | results = self.response_recognise(responses.status_code) 327 | return results 328 | 329 | # Get all layer groups 330 | async def get_all_layer_groups( 331 | self, workspace: Optional[str] = None 332 | ) -> Union[LayerGroupsModel, GSResponse]: 333 | Client = self.http_client 334 | if workspace: 335 | responses = await Client.get(f"workspaces/{workspace}/layergroups") 336 | else: 337 | responses = await Client.get("layergroups") 338 | if responses.status_code == 200: 339 | return LayerGroupsModel.model_validate(responses.json()) 340 | else: 341 | results = self.response_recognise(responses.status_code) 342 | return results 343 | 344 | # Get all geofence rules 345 | async def get_all_geofence_rules(self) -> Union[RulesResponse, GSResponse]: 346 | Client = self.http_client 347 | # Check if the geofence plugin exists 348 | module_check = await self.check_modules("geofence") 349 | # If the module check fails, return the GSResponse directly 350 | if isinstance(module_check, GSResponse): 351 | return module_check 352 | responses = await Client.get( 353 | "geofence/rules/", headers={"Accept": "application/json"} 354 | ) 355 | if responses.status_code == 200: 356 | return RulesResponse.model_validate(responses.json()) 357 | else: 358 | results = self.response_recognise(responses.status_code) 359 | return results 360 | 361 | # Get geofence rule by id 362 | async def get_geofence_rule(self, id: int) -> Union[Rule, GSResponse]: 363 | Client = self.http_client 364 | # Check if the geofence plugin exists 365 | module_check = await self.check_modules("geofence") 366 | # If the module check fails, return the GSResponse directly 367 | if isinstance(module_check, GSResponse): 368 | return module_check 369 | responses = await Client.get( 370 | f"geofence/rules/id/{id}", headers={"Accept": "application/json"} 371 | ) 372 | if responses.status_code == 200: 373 | return Rule.model_validate(responses.json()) 374 | else: 375 | results = self.response_recognise(responses.status_code) 376 | return results 377 | 378 | # Create geofence on geoserver 379 | async def create_geofence(self, rule: Rule) -> GSResponse: 380 | PostingRule = NewRule(Rule=rule) 381 | # Check if the geofence plugin exists 382 | module_check = await self.check_modules("geofence") 383 | # If the module check fails, return the GSResponse directly 384 | if isinstance(module_check, GSResponse): 385 | return module_check 386 | Client = self.http_client 387 | responses = await Client.post( 388 | "geofence/rules", 389 | content=PostingRule.model_dump_json(), 390 | headers=self.head, 391 | ) 392 | results = self.response_recognise(responses.status_code) 393 | return results 394 | -------------------------------------------------------------------------------- /src/geoserverx/_sync/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geobeyond/geoserverx/64349483b643d4c662da9d358286728360a38015/src/geoserverx/_sync/__init__.py -------------------------------------------------------------------------------- /src/geoserverx/models/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geobeyond/geoserverx/64349483b643d4c662da9d358286728360a38015/src/geoserverx/models/__init__.py -------------------------------------------------------------------------------- /src/geoserverx/models/coverages_layer.py: -------------------------------------------------------------------------------- 1 | from typing import List, Optional, Union 2 | 3 | from pydantic import BaseModel, Field 4 | 5 | 6 | class NativeCRS(BaseModel): 7 | class Config: 8 | populate_by_name = True 9 | 10 | crsclass: Optional[str] = Field(..., alias="@class") 11 | dollar: Optional[str] = Field(..., alias="$") 12 | 13 | 14 | class Namespace(BaseModel): 15 | name: Optional[str] = Field(None, description="The name of the namespace.") 16 | href: Optional[str] = Field(None, description="URL to the namespace.") 17 | 18 | 19 | class Keywords(BaseModel): 20 | string: Optional[List[str]] = Field( 21 | None, 22 | description="List of keyword values with internationalization and vocabulary", 23 | ) 24 | 25 | 26 | class MetadataLinkItem(BaseModel): 27 | type: Optional[str] = Field(None, description="The MIME type") 28 | metadataType: Optional[str] = Field( 29 | None, description='The type of metadata, e.g. "FGDC"' 30 | ) 31 | content: Optional[str] = Field(None, description="The link URL") 32 | 33 | 34 | class Metadatalinks(BaseModel): 35 | metadataLink: Optional[List[MetadataLinkItem]] = Field( 36 | None, description="A collection of metadata links for the resource." 37 | ) 38 | 39 | 40 | class MetadataLinkItem1(BaseModel): 41 | type: Optional[str] = Field(None, description="The MIME type") 42 | content: Optional[str] = Field(None, description="The link URL") 43 | 44 | 45 | class DataLinks(BaseModel): 46 | metadataLink: Optional[List[MetadataLinkItem1]] = Field( 47 | None, description="A collection of data links for the resource." 48 | ) 49 | 50 | 51 | class NativeBoundingBox(BaseModel): 52 | minx: Optional[float] = Field(None, description="The min x coordinate") 53 | maxx: Optional[float] = Field(None, description="The max x coordinate") 54 | miny: Optional[float] = Field(None, description="The min y coordinate") 55 | maxy: Optional[float] = Field(None, description="The max y coordinate") 56 | crs: Optional[NativeCRS] 57 | 58 | 59 | class LatLonBoundingBox(BaseModel): 60 | minx: Optional[float] = Field(None, description="The min x coordinate") 61 | maxx: Optional[float] = Field(None, description="The max x coordinate") 62 | miny: Optional[float] = Field(None, description="The min y coordinate") 63 | maxy: Optional[float] = Field(None, description="The max y coordinate") 64 | crs: Optional[str] = Field( 65 | None, description="The coordinate reference system object of the bounding box." 66 | ) 67 | 68 | 69 | class Store(BaseModel): 70 | class Config: 71 | populate_by_name = True 72 | 73 | storeclass: Optional[str] = Field(alias="@class") 74 | name: Optional[str] = Field(None, description="The name of the store") 75 | href: Optional[str] = Field(None, description="URL to the data store") 76 | 77 | 78 | class ResponseSRS(BaseModel): 79 | string: Optional[List[str]] = Field(None, description="The value of the srs") 80 | 81 | 82 | class AttributeItem(BaseModel): 83 | name: Optional[str] = Field(None, description="Name of the attribute.") 84 | minOccurs: Optional[int] = Field( 85 | None, description="Minimum number of occurrences of the attribute." 86 | ) 87 | maxOccurs: Optional[int] = Field( 88 | None, description="Maximum number of occurrences of the attribute." 89 | ) 90 | 91 | nillable: Optional[bool] = Field( 92 | None, 93 | description="Flag indicating if null is an acceptable value for the attribute.", 94 | ) 95 | binding: Optional[str] = Field( 96 | None, description="The java class that values of this attribute are bound to." 97 | ) 98 | length: Optional[int] = Field( 99 | None, 100 | description="Returns the length of this attribute. It's usually non null only for string and numeric types\"", 101 | ) 102 | 103 | 104 | class Attributes(BaseModel): 105 | attribute: Optional[List[AttributeItem]] = Field( 106 | None, description="The derived set of attributes for the feature type." 107 | ) 108 | 109 | 110 | class Range(BaseModel): 111 | max: Optional[Union[str, float]] = Field(None, description="max range value") 112 | min: Optional[Union[str, float]] = Field(None, description="min range value") 113 | 114 | 115 | class CoverageDimensionItem(BaseModel): 116 | description: Optional[str] = Field( 117 | None, description="description of the raster dimension" 118 | ) 119 | name: Optional[str] = Field(None, description="name of the dimension") 120 | range: Optional[Range] = Field(None, description="dimension range") 121 | 122 | 123 | class Dimensions(BaseModel): 124 | coverageDimension: Optional[List[CoverageDimensionItem]] = None 125 | 126 | 127 | class Range1(BaseModel): 128 | high: Optional[str] = Field(None, description="max range values") 129 | low: Optional[str] = Field(None, description="min range values") 130 | 131 | 132 | class Transform(BaseModel): 133 | scaleX: Optional[float] = Field(None, description="scale value to apply in X") 134 | scaleY: Optional[float] = Field(None, description="scale value to apply in Y") 135 | shearX: Optional[float] = Field(None, description="shear value to apply in X") 136 | shearY: Optional[float] = Field(None, description="shear value to apply in Y") 137 | translateX: Optional[float] = Field(None, description="translation to apply in X") 138 | translateY: Optional[float] = Field(None, description="translation to apply in Y") 139 | 140 | 141 | class InterpolationMethods(BaseModel): 142 | string: Optional[List[str]] = None 143 | 144 | 145 | class SupportedFormatsString(BaseModel): 146 | string: Optional[List[str]] = None 147 | 148 | 149 | class RequestSRSString(BaseModel): 150 | string: Optional[List[str]] = None 151 | 152 | 153 | class InterpolationMethodsString(BaseModel): 154 | string: Optional[List[str]] = None 155 | 156 | 157 | class Grid(BaseModel): 158 | class Config: 159 | populate_by_name = True 160 | 161 | dimension: Optional[str] = Field(alias="@dimension") 162 | crs: Optional[str] = Field(None, description="target coordinate system") 163 | range: Optional[Range1] = Field(None, description="range of the raster plan") 164 | transform: Optional[Transform] = Field( 165 | None, description="transformation definition" 166 | ) 167 | interpolationMethods: Optional[InterpolationMethods] = Field( 168 | None, description="available interpolations methods for this coverage" 169 | ) 170 | 171 | 172 | class DimensionInfo(BaseModel): 173 | defaultValue: Optional[str] 174 | enabled: Optional[bool] 175 | 176 | 177 | class MetadataEntry(BaseModel): 178 | class Config: 179 | populate_by_name = True 180 | 181 | key: Optional[str] = Field(alias="@key") 182 | dollar: Optional[str] = Field(..., alias="$") 183 | dimensionInfo: Optional[DimensionInfo] 184 | 185 | 186 | class EntryParameters(BaseModel): 187 | entry: List 188 | 189 | 190 | class MetadataEntryList(BaseModel): 191 | entry: MetadataEntry 192 | 193 | 194 | class CoverageInfo(BaseModel): 195 | name: Optional[str] = Field( 196 | None, 197 | description='The name of the resource. This name corresponds to the "published" name of the resource.', 198 | ) 199 | nativeName: Optional[str] = Field( 200 | None, 201 | description="The native name of the resource. This name corresponds to the physical resource that feature type is derived from -- a shapefile name, a database table, etc...", 202 | ) 203 | namespace: Optional[Namespace] = Field( 204 | None, 205 | description="The namespace URI of the resource. Example would be an application schema namespace URI.", 206 | ) 207 | title: Optional[str] = Field( 208 | None, 209 | description="The title of the resource. This is usually something that is meant to be displayed in a user interface.", 210 | ) 211 | abstract: Optional[str] = Field( 212 | None, 213 | description="A description of the resource. This is usually something that is meant to be displayed in a user interface.", 214 | ) 215 | defaultInterpolationMethod: Optional[str] = Field( 216 | None, 217 | description="Default resampling (interpolation) method that will be used for this coverage.", 218 | ) 219 | keywords: Optional[Keywords] = Field( 220 | None, description="A collection of keywords associated with the resource." 221 | ) 222 | metadatalinks: Optional[Metadatalinks] = Field( 223 | None, description="Wraps a collection of metadata links for the resource." 224 | ) 225 | dataLinks: Optional[DataLinks] = Field( 226 | None, description="Wraps a collection of data links for the resource." 227 | ) 228 | nativeCRS: Optional[NativeCRS] 229 | srs: Optional[str] = Field( 230 | None, 231 | description="Returns the identifier of coordinate reference system of the resource.", 232 | ) 233 | nativeBoundingBox: Optional[NativeBoundingBox] = Field( 234 | None, description="Returns the bounds of the resource in its declared CRS." 235 | ) 236 | latLonBoundingBox: Optional[LatLonBoundingBox] = Field( 237 | None, 238 | description='The bounds of the resource in lat / lon. This value represents a "fixed value" and is not calculated on the underlying dataset.', 239 | ) 240 | enabled: Optional[bool] = True 241 | advertised: Optional[bool] = True 242 | projectionPolicy: Optional[str] 243 | metadata: Optional[MetadataEntryList] = Field( 244 | None, description="A list of key/value metadata pairs." 245 | ) 246 | store: Optional[Store] 247 | cqlFilter: Optional[str] = Field( 248 | None, description="The ECQL string used as default feature type filter" 249 | ) 250 | maxFeatures: Optional[int] = Field( 251 | None, 252 | description="A cap on the number of features that a query against this type can return.", 253 | ) 254 | numDecimals: Optional[float] = Field( 255 | None, 256 | description="The number of decimal places to use when encoding floating point numbers from data of this feature type.", 257 | ) 258 | responseSRS: Optional[ResponseSRS] = Field( 259 | None, 260 | description="The SRSs that the WFS service will advertise in the capabilities document for this feature type (overriding the global WFS settings).", 261 | ) 262 | overridingServiceSRS: Optional[bool] = Field( 263 | None, 264 | description="True if this feature type info is overriding the WFS global SRS list", 265 | ) 266 | skipNumberMatched: Optional[bool] = Field( 267 | None, 268 | description="True if this feature type info is overriding the counting of numberMatched.", 269 | ) 270 | circularArcPresent: Optional[bool] = None 271 | linearizationTolerance: Optional[float] = Field( 272 | None, 273 | description="Tolerance used to linearize this feature type, as an absolute value expressed in the geometries own CRS", 274 | ) 275 | attributes: Optional[Attributes] = Field( 276 | None, 277 | description="Wrapper for the derived set of attributes for the feature type.", 278 | ) 279 | dimensions: Optional[Dimensions] = Field(None, description="raster dimensions") 280 | grid: Optional[Grid] = Field( 281 | None, 282 | description="contains information about how to translate from the raster plan to a coordinate reference system", 283 | ) 284 | supportedFormats: Optional[SupportedFormatsString] 285 | interpolationMethods: Optional[InterpolationMethodsString] 286 | requestSRS: Optional[RequestSRSString] 287 | parameters: EntryParameters 288 | serviceConfiguration: Optional[bool] 289 | simpleConversionEnabled: Optional[bool] 290 | 291 | 292 | class CoverageModel(BaseModel): 293 | coverage: CoverageInfo 294 | 295 | 296 | class UpdateCoverage: 297 | def __init__(self) -> None: 298 | pass 299 | 300 | def update_coverage_info( 301 | self, coverage_info: CoverageInfo, advertised: Optional[bool] = True 302 | ) -> CoverageInfo: 303 | if advertised: 304 | coverage_info.advertised = advertised 305 | 306 | return coverage_info 307 | -------------------------------------------------------------------------------- /src/geoserverx/models/coverages_store.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, List, Literal, Optional, Union 2 | 3 | from pydantic import BaseModel 4 | 5 | from geoserverx.models.workspace import WorkspaceInBulk 6 | 7 | 8 | class CoveragesStoreInBulk(BaseModel): 9 | name: str = ... 10 | href: str = ... 11 | 12 | 13 | class CoveragesStoresDict(BaseModel): 14 | coverageStore: List[CoveragesStoreInBulk] = ... 15 | 16 | 17 | class CoveragesStoresModel(BaseModel): 18 | coverageStores: Union[CoveragesStoresDict, Literal[""]] = "" 19 | 20 | 21 | class CoveragesStoreModelDetail(BaseModel): 22 | name: str = ... 23 | description: str = None 24 | type: str = ... 25 | enabled: bool = ... 26 | workspace: WorkspaceInBulk = ... 27 | _default: bool = ... 28 | url: str = ... 29 | coverages: str = ... 30 | dateCreated: Optional[str] 31 | metadata: Optional[Dict] = None 32 | 33 | 34 | class CoveragesStoreModel(BaseModel): 35 | coverageStore: CoveragesStoreModelDetail 36 | -------------------------------------------------------------------------------- /src/geoserverx/models/data_store.py: -------------------------------------------------------------------------------- 1 | from typing import List, Literal, Optional, Union 2 | 3 | from pydantic import BaseModel, Field 4 | 5 | from .workspace import WorkspaceInBulk 6 | 7 | 8 | class DataStoreInBulk(BaseModel): 9 | name: str = ... 10 | href: str = ... 11 | 12 | 13 | class DataStoreDict(BaseModel): 14 | dataStore: List[DataStoreInBulk] 15 | 16 | 17 | class DataStoresModel(BaseModel): 18 | dataStores: Union[DataStoreDict, Literal[""]] = "" 19 | 20 | 21 | class DatastoreConnection(BaseModel): 22 | key: str = Field(..., alias="@key") 23 | path: str = Field(..., alias="$") 24 | 25 | class Config: 26 | populate_by_name = True 27 | 28 | 29 | class EntryItem(BaseModel): 30 | entry: List[DatastoreConnection] 31 | 32 | 33 | class DatastoreItem(BaseModel): 34 | name: str 35 | connectionParameters: EntryItem 36 | 37 | 38 | class DataStoreModelDetails(BaseModel): 39 | name: str = ... 40 | description: str = None 41 | enabled: bool = ... 42 | workspace: WorkspaceInBulk = ... 43 | connectionParameters: EntryItem = ... 44 | _default: bool = ... 45 | dateCreated: Optional[str] 46 | dateModified: Optional[str] 47 | featureTypes: str 48 | 49 | 50 | class DataStoreModel(BaseModel): 51 | dataStore: DataStoreModelDetails = {} 52 | 53 | 54 | class CreateStoreItem(BaseModel): 55 | host: str 56 | port: int 57 | database: str 58 | user: str 59 | passwd: str 60 | dbtype: str 61 | 62 | 63 | class CreateDataStoreModel(BaseModel): 64 | name: str 65 | connectionParameters: CreateStoreItem 66 | 67 | 68 | class MainCreateDataStoreModel(BaseModel): 69 | dataStore: CreateDataStoreModel 70 | -------------------------------------------------------------------------------- /src/geoserverx/models/featuretypes_layer.py: -------------------------------------------------------------------------------- 1 | from typing import List, Optional, Union 2 | 3 | from pydantic import BaseModel, Field 4 | 5 | 6 | class Namespace(BaseModel): 7 | name: Optional[str] = Field(None, description="The name of the namespace.") 8 | href: Optional[str] = Field(None, description="URL to the namespace.") 9 | 10 | 11 | class Keywords(BaseModel): 12 | string: Optional[List[str]] = Field( 13 | None, 14 | description="List of keyword values with internationalization and vocabulary", 15 | ) 16 | 17 | 18 | class MetadataLinkItem(BaseModel): 19 | type: Optional[str] = Field(None, description="The MIME type") 20 | metadataType: Optional[str] = Field( 21 | None, description='The type of metadata, e.g. "FGDC"' 22 | ) 23 | content: Optional[str] = Field(None, description="The link URL") 24 | 25 | 26 | class Metadatalinks(BaseModel): 27 | metadataLink: Optional[List[MetadataLinkItem]] = Field( 28 | None, description="A collection of metadata links for the resource." 29 | ) 30 | 31 | 32 | class MetadataLinkItem1(BaseModel): 33 | type: Optional[str] = Field(None, description="The MIME type") 34 | content: Optional[str] = Field(None, description="The link URL") 35 | 36 | 37 | class DataLinks(BaseModel): 38 | metadataLink: Optional[List[MetadataLinkItem1]] = Field( 39 | None, description="A collection of data links for the resource." 40 | ) 41 | 42 | 43 | class CRSentry(BaseModel): 44 | class Config: 45 | populate_by_name = True 46 | 47 | key: Optional[str] = Field(alias="@class") 48 | dollar: Optional[str] = Field(..., alias="$") 49 | 50 | 51 | class CRSetnryDict(BaseModel): 52 | entry: CRSentry 53 | 54 | 55 | class NativeBoundingBox(BaseModel): 56 | minx: Optional[float] = Field(None, description="The min x coordinate") 57 | maxx: Optional[float] = Field(None, description="The max x coordinate") 58 | miny: Optional[float] = Field(None, description="The min y coordinate") 59 | maxy: Optional[float] = Field(None, description="The max y coordinate") 60 | crs: Optional[Union[str, CRSentry]] = Field( 61 | None, description="The coordinate reference system object of the bounding box." 62 | ) 63 | 64 | 65 | class LatLonBoundingBox(BaseModel): 66 | minx: Optional[float] = Field(None, description="The min x coordinate") 67 | maxx: Optional[float] = Field(None, description="The max x coordinate") 68 | miny: Optional[float] = Field(None, description="The min y coordinate") 69 | maxy: Optional[float] = Field(None, description="The max y coordinate") 70 | crs: Optional[str] = Field( 71 | None, description="The coordinate reference system object of the bounding box." 72 | ) 73 | 74 | 75 | class Store(BaseModel): 76 | class Config: 77 | populate_by_name = True 78 | 79 | key: Optional[str] = Field(alias="@class") 80 | name: Optional[str] = Field(None, description="The name of the store") 81 | href: Optional[str] = Field(None, description="URL to the data store") 82 | 83 | 84 | class ResponseSRS(BaseModel): 85 | string: Optional[str] = Field(None, description="The value of the srs") 86 | 87 | 88 | class AttributeItem(BaseModel): 89 | name: Optional[str] = Field(None, description="Name of the attribute.") 90 | minOccurs: Optional[int] = Field( 91 | None, description="Minimum number of occurrences of the attribute." 92 | ) 93 | maxOccurs: Optional[int] = Field( 94 | None, description="Maximum number of occurrences of the attribute." 95 | ) 96 | nillable: Optional[bool] = Field( 97 | None, 98 | description="Flag indicating if null is an acceptable value for the attribute.", 99 | ) 100 | binding: Optional[str] = Field( 101 | None, description="The java class that values of this attribute are bound to." 102 | ) 103 | length: Optional[int] 104 | 105 | 106 | class Attributes(BaseModel): 107 | attribute: Optional[List[AttributeItem]] = Field( 108 | None, description="The derived set of attributes for the feature type." 109 | ) 110 | 111 | 112 | class MetadataEntryItem(BaseModel): 113 | class Config: 114 | populate_by_name = True 115 | 116 | key: Optional[str] = Field(alias="@key") 117 | dollar: Optional[str] = Field(..., alias="$") 118 | 119 | 120 | class MetadataEntryList(BaseModel): 121 | entry: List[MetadataEntryItem] 122 | 123 | 124 | class FeatureTypeInfo(BaseModel): 125 | name: Optional[str] = Field( 126 | None, 127 | description='The name of the resource. This name corresponds to the "published" name of the resource.', 128 | ) 129 | nativeName: Optional[str] = Field( 130 | None, 131 | description="The native name of the resource. This name corresponds to the physical resource that feature type is derived from -- a shapefile name, a database table, etc...", 132 | ) 133 | namespace: Optional[Namespace] = Field( 134 | None, 135 | description="The namespace URI of the resource. Example would be an application schema namespace URI.", 136 | ) 137 | title: Optional[str] = Field( 138 | None, 139 | description="The title of the resource. This is usually something that is meant to be displayed in a user interface.", 140 | ) 141 | abstract: Optional[str] = Field( 142 | None, 143 | description="A description of the resource. This is usually something that is meant to be displayed in a user interface.", 144 | ) 145 | keywords: Optional[Keywords] = Field( 146 | None, description="A collection of keywords associated with the resource." 147 | ) 148 | srs: Optional[str] = Field( 149 | None, 150 | description="Returns the identifier of coordinate reference system of the resource.", 151 | ) 152 | metadatalinks: Optional[Metadatalinks] 153 | dataLinks: Optional[DataLinks] 154 | 155 | nativeBoundingBox: Optional[NativeBoundingBox] = Field( 156 | None, description="Returns the bounds of the resource in its declared CRS." 157 | ) 158 | latLonBoundingBox: Optional[LatLonBoundingBox] = Field( 159 | None, 160 | description='The bounds of the resource in lat / lon. This value represents a "fixed value" and is not calculated on the underlying dataset.', 161 | ) 162 | projectionPolicy: str 163 | metadata: Optional[MetadataEntryList] = Field( 164 | None, description="A list of key/value metadata pairs." 165 | ) 166 | store: Optional[Store] = Field( 167 | None, description="The store the resource is a part of." 168 | ) 169 | nativeCRS: Optional[Union[str, CRSentry]] = Field( 170 | None, description="String for Native CRS" 171 | ) 172 | cqlFilter: Optional[str] = Field( 173 | None, description="The ECQL string used as default feature type filter" 174 | ) 175 | maxFeatures: Optional[int] = Field( 176 | None, 177 | description="A cap on the number of features that a query against this type can return.", 178 | ) 179 | numDecimals: Optional[int] = Field( 180 | None, 181 | description="The number of decimal places to use when encoding floating point numbers from data of this feature type.", 182 | ) 183 | responseSRS: Optional[ResponseSRS] = Field( 184 | None, 185 | description="The SRSs that the WFS service will advertise in the capabilities document for this feature type (overriding the global WFS settings).", 186 | ) 187 | overridingServiceSRS: Optional[bool] = Field( 188 | None, 189 | description="True if this feature type info is overriding the WFS global SRS list", 190 | ) 191 | skipNumberMatched: Optional[bool] = Field( 192 | None, 193 | description="True if this feature type info is overriding the counting of numberMatched.", 194 | ) 195 | circularArcPresent: Optional[bool] = None 196 | linearizationTolerance: Optional[float] = Field( 197 | None, 198 | description="Tolerance used to linearize this feature type, as an absolute value expressed in the geometries own CRS", 199 | ) 200 | attributes: Optional[Attributes] = Field( 201 | None, 202 | description="Wrapper for the derived set of attributes for the feature type.", 203 | ) 204 | enabled: Optional[bool] 205 | advertised: Optional[bool] 206 | serviceConfiguration: Optional[bool] 207 | simpleConversionEnabled: Optional[bool] 208 | padWithZeros: Optional[bool] 209 | forcedDecimal: Optional[bool] 210 | overridingServiceSRS: Optional[bool] 211 | skipNumberMatched: Optional[bool] 212 | circularArcPresent: Optional[bool] 213 | encodeMeasures: Optional[bool] 214 | 215 | 216 | class FeatureTypesModel(BaseModel): 217 | featureType: FeatureTypeInfo 218 | -------------------------------------------------------------------------------- /src/geoserverx/models/geofence.py: -------------------------------------------------------------------------------- 1 | from typing import List, Literal, Optional 2 | 3 | from pydantic import BaseModel 4 | 5 | 6 | class Attribute(BaseModel): 7 | name: str 8 | dataType: str 9 | accessType: str 10 | 11 | 12 | class LayerDetails(BaseModel): 13 | layerType: Optional[str] = None 14 | defaultStyle: Optional[str] = None 15 | cqlFilterRead: Optional[str] = None 16 | cqlFilterWrite: Optional[str] = None 17 | allowedArea: Optional[str] = None 18 | spatialFilterType: Optional[str] = None 19 | catalogMode: Optional[str] = None 20 | allowedStyles: List[str] = [] 21 | attributes: List[Attribute] 22 | 23 | 24 | class Rule(BaseModel): 25 | priority: int 26 | userName: Optional[str] = None 27 | roleName: Optional[str] = None 28 | addressRange: Optional[str] = None 29 | workspace: Optional[str] = None 30 | layer: Optional[str] = None 31 | service: Optional[Literal["GWC", "WMS", "WCS", "WFS"]] = None 32 | request: Optional[str] = None 33 | subfield: Optional[str] = None 34 | access: Literal["ALLOW", "DENY", "LIMIT"] = "ALLOW" 35 | limits: Optional[str] = None 36 | layerDetails: Optional[LayerDetails] = None 37 | 38 | 39 | class GetRule(Rule): 40 | id: Optional[int] = None 41 | 42 | 43 | class RulesResponse(BaseModel): 44 | count: int 45 | rules: List[GetRule] 46 | 47 | 48 | class NewRule(BaseModel): 49 | Rule: Rule 50 | -------------------------------------------------------------------------------- /src/geoserverx/models/gs_response.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from pydantic import BaseModel 4 | 5 | 6 | class GSResponse(BaseModel): 7 | code: Optional[int] 8 | response: str = ... 9 | 10 | 11 | class HttpxError(BaseModel): 12 | response: str 13 | -------------------------------------------------------------------------------- /src/geoserverx/models/layer_group.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | from typing import List, Literal, Optional, Union 3 | 4 | from pydantic import BaseModel, Field 5 | 6 | 7 | class LayerGroupElement(BaseModel): 8 | name: str = ... 9 | href: str = ... 10 | 11 | 12 | class LayerGroupList(BaseModel): 13 | layerGroup: List[LayerGroupElement] = ... 14 | 15 | 16 | class LayerGroupsModel(BaseModel): 17 | layerGroups: Union[LayerGroupList, Literal[""]] 18 | 19 | 20 | class Published(BaseModel): 21 | type: str = Field(..., alias="@type") 22 | name: str = ... 23 | href: str = ... 24 | 25 | 26 | class Publishables(BaseModel): 27 | published: Published = ... 28 | 29 | 30 | class Style(BaseModel): 31 | name: str = ... 32 | href: str = ... 33 | 34 | 35 | class Styles(BaseModel): 36 | style: Style = ... 37 | 38 | 39 | class Bounds(BaseModel): 40 | minx: float = ... 41 | miny: float = ... 42 | maxx: float = ... 43 | maxy: float = ... 44 | crs: str = ... 45 | 46 | 47 | class ModeEnum(Enum): 48 | single = "SINGLE" 49 | opaque_container = "OPAQUE_CONTAINER" 50 | named = "NAMED" 51 | container = "CONTAINER" 52 | eo = "EO" 53 | 54 | 55 | class WorkspaceModel(BaseModel): 56 | name: str = None 57 | 58 | 59 | class BaseLayerGroup(BaseModel): 60 | name: str = ... 61 | 62 | 63 | class SingleLayerGroup(BaseLayerGroup): 64 | mode: ModeEnum 65 | internationalTitle: str = "" 66 | internationalAbstract: str = "" 67 | publishables: Publishables 68 | styles: Styles 69 | bounds: Bounds 70 | dateCreated: str = ... 71 | 72 | 73 | class SingleLayerGroupModel(BaseModel): 74 | layerGroup: SingleLayerGroup 75 | 76 | 77 | class LayerListModel(BaseModel): 78 | layer: List[str] = [] 79 | 80 | 81 | class LayerGroupModel(BaseModel): 82 | name: str 83 | mode: ModeEnum 84 | title: str 85 | layers: LayerListModel 86 | abstractTxt: Optional[str] = None 87 | workspace: Optional[WorkspaceModel] = None 88 | 89 | 90 | class LayerGroupPayload(BaseModel): 91 | layerGroup: LayerGroupModel 92 | 93 | 94 | class LayerGroupStylesModel(BaseModel): 95 | style: List[str] = [] 96 | 97 | 98 | class LayerGroupKeywordsModel(BaseModel): 99 | keyword: List[str] = [] 100 | -------------------------------------------------------------------------------- /src/geoserverx/models/layers.py: -------------------------------------------------------------------------------- 1 | from typing import List, Optional, Union 2 | 3 | from pydantic import BaseModel, Field 4 | 5 | 6 | class LayerInBulk(BaseModel): 7 | name: str = ... 8 | href: str = ... 9 | 10 | 11 | class LayerDict(BaseModel): 12 | layer: List[LayerInBulk] 13 | 14 | 15 | class LayersModel(BaseModel): 16 | layers: Union[LayerDict, str] = "" 17 | 18 | 19 | class DefaultStyleOfLayer(BaseModel): 20 | name: str = ... 21 | href: str = ... 22 | 23 | 24 | class ExtraStyles(BaseModel): 25 | class_name: str = Field(..., alias="@class") 26 | style: List[DefaultStyleOfLayer] 27 | 28 | 29 | class LayerResource(BaseModel): 30 | class_name: str = Field(..., alias="@class") 31 | name: str = ... 32 | href: str = ... 33 | 34 | 35 | class LayerAttribution(BaseModel): 36 | logoWidth: float = ... 37 | logoHeight: float = ... 38 | 39 | 40 | class SingleLayer(BaseModel): 41 | name: str = ... 42 | path: Optional[str] 43 | type: str = ... 44 | defaultStyle: DefaultStyleOfLayer = ... 45 | styles: Optional[ExtraStyles] = None 46 | resource: LayerResource = ... 47 | attribution: LayerAttribution 48 | dateCreated: Optional[str] = None 49 | opaque: Optional[bool] 50 | queryable: Optional[bool] 51 | 52 | 53 | class LayerModel(BaseModel): 54 | layer: SingleLayer = ... 55 | 56 | 57 | # class NewWorkspaceInfo(BaseModel): 58 | # name: str = ... 59 | # isolated: bool = None 60 | 61 | 62 | # class NewWorkspace(BaseModel): 63 | # workspace: NewWorkspaceInfo = ... 64 | -------------------------------------------------------------------------------- /src/geoserverx/models/style.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | from pydantic import BaseModel 4 | 5 | 6 | class langVersion(BaseModel): 7 | version: str = ... 8 | 9 | 10 | class SingleStyle(BaseModel): 11 | name: str = ... 12 | format: str = ... 13 | languageVersion: langVersion = ... 14 | filename: str = ... 15 | 16 | 17 | class StyleModel(BaseModel): 18 | style: SingleStyle 19 | 20 | 21 | class allStyleList(BaseModel): 22 | name: str 23 | href: str 24 | 25 | 26 | class allStyle(BaseModel): 27 | style: List[allStyleList] 28 | 29 | 30 | class AllStylesModel(BaseModel): 31 | styles: allStyle 32 | -------------------------------------------------------------------------------- /src/geoserverx/models/workspace.py: -------------------------------------------------------------------------------- 1 | from typing import List, Optional 2 | 3 | from pydantic import BaseModel 4 | 5 | 6 | class WorkspaceInBulk(BaseModel): 7 | name: str = ... 8 | href: str = ... 9 | 10 | 11 | class workspaceDict(BaseModel): 12 | workspace: List[WorkspaceInBulk] 13 | 14 | 15 | class WorkspacesModel(BaseModel): 16 | workspaces: workspaceDict = "" 17 | 18 | 19 | class SingleWorkspace(BaseModel): 20 | name: str = ... 21 | isolated: bool = ... 22 | dateCreated: Optional[str] 23 | dataStores: str = ... 24 | coverageStores: str = ... 25 | wmsStores: str = ... 26 | wmtsStores: str = ... 27 | 28 | 29 | class WorkspaceModel(BaseModel): 30 | workspace: SingleWorkspace = ... 31 | 32 | 33 | class NewWorkspaceInfo(BaseModel): 34 | name: str = ... 35 | isolated: bool = None 36 | 37 | 38 | class NewWorkspace(BaseModel): 39 | workspace: NewWorkspaceInfo = ... 40 | -------------------------------------------------------------------------------- /src/geoserverx/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geobeyond/geoserverx/64349483b643d4c662da9d358286728360a38015/src/geoserverx/utils/__init__.py -------------------------------------------------------------------------------- /src/geoserverx/utils/auth.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from geoserverx.utils.errors import GeoServerXError 4 | 5 | 6 | @dataclass 7 | class GeoServerXAuth: 8 | username: str = "admin" 9 | password: str = "geoserver" 10 | url = "http://127.0.0.1:8080/geoserver/rest/" 11 | 12 | def __post_init__(self): 13 | if not self.username and not self.password and not self.url: 14 | raise GeoServerXError(0, "Username, Password and URL is missing") 15 | elif not self.username: 16 | raise GeoServerXError(0, "Username is missing") 17 | elif not self.password: 18 | raise GeoServerXError(0, "password is missing") 19 | elif not self.url: 20 | raise GeoServerXError(0, "URL is missing") 21 | -------------------------------------------------------------------------------- /src/geoserverx/utils/custom_exceptions.py: -------------------------------------------------------------------------------- 1 | class GSModuleNotFound(Exception): 2 | def __init__(self, message="Module not found", status_code=412): 3 | self.message = message 4 | self.status_code = status_code 5 | super().__init__(self.message) 6 | -------------------------------------------------------------------------------- /src/geoserverx/utils/enums.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | from geoserverx.models.gs_response import GSResponse 4 | 5 | 6 | class GSResponseEnum(Enum): 7 | _404 = GSResponse(code=404, response="Result not found") 8 | _403 = GSResponse(code=403, response="Forbidden Request") 9 | _401 = GSResponse(code=401, response="Unauthorized request") 10 | _500 = GSResponse(code=500, response="Internal Server error") 11 | _201 = GSResponse(code=201, response="Data added successfully") 12 | _200 = GSResponse(code=200, response="Executed successfully") 13 | _204 = GSResponse(code=204, response="No Content") 14 | _400 = GSResponse(code=400, response="Bad Request") 15 | _409 = GSResponse(code=409, response="Same data found") 16 | _503 = GSResponse(code=503, response="Can't connect to Geoserver") 17 | 18 | 19 | class HTTPXErrorEnum(Enum): 20 | runtime = "Client not found! Please check client parameters" 21 | requesterr = "Client Credentials are incorrect" 22 | -------------------------------------------------------------------------------- /src/geoserverx/utils/errors.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass, field 2 | from typing import Optional 3 | 4 | 5 | @dataclass 6 | class GeoServerXError(Exception): 7 | status_code: int 8 | status_message: Optional[str] = field(default=None) 9 | -------------------------------------------------------------------------------- /src/geoserverx/utils/http_client.py: -------------------------------------------------------------------------------- 1 | from httpx import AsyncClient # noqa: F401 2 | from httpx import Client as BaseClient # noqa: F401 3 | 4 | 5 | class SyncClient(BaseClient): 6 | def aclose(self) -> None: 7 | self.close() 8 | -------------------------------------------------------------------------------- /src/geoserverx/utils/logger.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import sys 3 | 4 | 5 | def std_out_logger(name: str) -> logging.Logger: 6 | logger = logging.getLogger(name) 7 | logger.setLevel(logging.DEBUG) 8 | handler = logging.StreamHandler(sys.stdout) 9 | formatter = logging.Formatter("[%(name)s] [%(levelname)s] %(message)s") 10 | handler.setFormatter(formatter) 11 | logger.addHandler(handler) 12 | return logger 13 | -------------------------------------------------------------------------------- /src/geoserverx/utils/services/async_datastore.py: -------------------------------------------------------------------------------- 1 | import json 2 | from logging import Logger 3 | from typing import Protocol 4 | 5 | 6 | class AddDataStoreProtocol(Protocol): 7 | """ 8 | Represents functionality of sending a file to the server. 9 | """ 10 | 11 | async def addFile( 12 | self, 13 | client, 14 | workspace, 15 | store, 16 | method, 17 | store_payload, 18 | layer_payload, 19 | store_header, 20 | layer_header, 21 | ): ... 22 | 23 | 24 | class CreateFileStore: 25 | async def addFile( 26 | self, 27 | client, 28 | workspace, 29 | store, 30 | method, 31 | store_payload, 32 | layer_payload, 33 | store_header, 34 | layer_header, 35 | ): 36 | Client = client 37 | # async with client as Client: 38 | store_responses = await Client.post( 39 | f"workspaces/{workspace}/datastores/", 40 | data=store_payload, 41 | headers=store_header, 42 | ) 43 | # async with client as Client: 44 | await Client.put( 45 | f"workspaces/{workspace}/datastores/{store}/file.{method}", 46 | content=layer_payload, 47 | headers=layer_header, 48 | ) 49 | results = store_responses.status_code 50 | # await client.aclose() 51 | 52 | return results 53 | 54 | 55 | class ShapefileStore: 56 | def __init__( 57 | self, service: AddDataStoreProtocol, logger: Logger, file, client 58 | ) -> None: 59 | self.inner = service 60 | self.logger = logger 61 | self.file = file 62 | self.client = client 63 | 64 | async def addFile(self, client, workspace, store): 65 | store_payload: str = json.dumps( 66 | { 67 | "dataStore": { 68 | "name": store, 69 | "connectionParameters": { 70 | "entry": [{"@key": "url", "$": "file:" + self.file}] 71 | }, 72 | } 73 | } 74 | ) 75 | # self.logger.debug(f"Shapefile store payload: {store_payload}") 76 | result = await self.inner.addFile( 77 | self.client, 78 | workspace, 79 | store, 80 | "shp", 81 | store_payload, 82 | {"Content-Type": "application/json"}, 83 | {"Content-Type": "application/zip"}, 84 | ) 85 | return result 86 | 87 | 88 | class GPKGfileStore: 89 | def __init__( 90 | self, service: AddDataStoreProtocol, logger: Logger, file, client 91 | ) -> None: 92 | self.inner = service 93 | self.logger = logger 94 | self.file = file 95 | self.client = client 96 | 97 | async def addFile(self, client, workspace, store): 98 | store_payload: str = json.dumps( 99 | { 100 | "dataStore": { 101 | "name": store, 102 | "connectionParameters": { 103 | "entry": [ 104 | {"@key": "database", "$": f"file:{self.file}"}, 105 | {"@key": "dbtype", "$": "geopkg"}, 106 | ] 107 | }, 108 | } 109 | } 110 | ) 111 | # self.logger.debug(f"GeoPackage store payload: {store_payload}") 112 | result = await self.inner.addFile( 113 | self.client, 114 | workspace, 115 | store, 116 | "gpkg", 117 | store_payload, 118 | {"Content-Type": "application/json"}, 119 | ) 120 | return result 121 | -------------------------------------------------------------------------------- /src/geoserverx/utils/services/datastore.py: -------------------------------------------------------------------------------- 1 | import json 2 | from logging import Logger 3 | from typing import Protocol 4 | 5 | 6 | class AddDataStoreProtocol(Protocol): 7 | """ 8 | Represents functionality of sending a file to the server. 9 | """ 10 | 11 | def addFile( 12 | self, 13 | client, 14 | workspace, 15 | store, 16 | method, 17 | store_payload, 18 | layer_payload, 19 | store_header, 20 | layer_header, 21 | ): ... 22 | 23 | 24 | class CreateFileStore: 25 | def addFile( 26 | self, 27 | client, 28 | workspace, 29 | store, 30 | method, 31 | store_payload, 32 | layer_payload, 33 | store_header, 34 | layer_header, 35 | ): 36 | store_responses = client.post( 37 | f"workspaces/{workspace}/datastores/", 38 | content=store_payload, 39 | headers=store_header, 40 | ) 41 | client.put( 42 | f"workspaces/{workspace}/datastores/{store}/file.{method}", 43 | content=layer_payload, 44 | headers=layer_header, 45 | ) 46 | result = store_responses.status_code 47 | return result 48 | 49 | 50 | class ShapefileStore: 51 | def __init__(self, service: AddDataStoreProtocol, logger: Logger, file) -> None: 52 | self.inner = service 53 | self.logger = logger 54 | self.file = file 55 | self.result = None 56 | 57 | def addFile(self, client, workspace, store): 58 | store_payload: str = json.dumps( 59 | { 60 | "dataStore": { 61 | "name": store, 62 | "connectionParameters": { 63 | "entry": [{"@key": "url", "$": f"file:{self.file}"}] 64 | }, 65 | } 66 | } 67 | ) 68 | # self.logger.debug(f"Shapefile store payload: {store_payload}") 69 | layer_payload = self.file 70 | response = self.inner.addFile( 71 | client, 72 | workspace, 73 | store, 74 | "shp", 75 | store_payload, 76 | layer_payload, 77 | {"Content-Type": "application/json"}, 78 | {"Content-Type": "application/zip"}, 79 | ) 80 | self.result = response 81 | return self.result 82 | 83 | 84 | class GPKGfileStore: 85 | def __init__(self, service: AddDataStoreProtocol, logger: Logger, file) -> None: 86 | self.inner = service 87 | self.logger = logger 88 | self.file = file 89 | self.result = None 90 | 91 | def addFile(self, client, workspace, store): 92 | store_payload: str = json.dumps( 93 | { 94 | "dataStore": { 95 | "name": store, 96 | "connectionParameters": { 97 | "entry": [ 98 | {"@key": "database", "$": f"file:{self.file}"}, 99 | {"@key": "dbtype", "$": "geopkg"}, 100 | ] 101 | }, 102 | } 103 | } 104 | ) 105 | # self.logger.debug(f"GeoPackage store payload: {store_payload}") 106 | layer_payload = self.file 107 | response = self.inner.addFile( 108 | client, 109 | workspace, 110 | store, 111 | "gpkg", 112 | store_payload, 113 | layer_payload, 114 | {"Content-Type": "application/json"}, 115 | {"Content-Type": "application/json"}, 116 | ) 117 | self.result = response 118 | return self.result 119 | -------------------------------------------------------------------------------- /tests/_async/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geobeyond/geoserverx/64349483b643d4c662da9d358286728360a38015/tests/_async/__init__.py -------------------------------------------------------------------------------- /tests/_sync/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geobeyond/geoserverx/64349483b643d4c662da9d358286728360a38015/tests/_sync/__init__.py -------------------------------------------------------------------------------- /tests/cli/test_cli.py: -------------------------------------------------------------------------------- 1 | import httpx 2 | from typer.testing import CliRunner 3 | 4 | from geoserverx.cli.cli import app 5 | 6 | runner = CliRunner() 7 | 8 | baseUrl = "http://127.0.0.1:8080/geoserver/rest/" 9 | 10 | 11 | # Test - get_all_workspaces 12 | def test_get_all_workspaces_validation(bad_workspaces_connection, respx_mock): 13 | respx_mock.get(f"{baseUrl}workspaces").mock( 14 | return_value=httpx.Response(404, json=bad_workspaces_connection) 15 | ) 16 | result = runner.invoke(app, ["workspaces"]) 17 | assert "404" in result.stdout 18 | 19 | 20 | def test_get_all_workspaces_success(good_workspaces_connection, respx_mock): 21 | respx_mock.get(f"{baseUrl}workspaces").mock( 22 | return_value=httpx.Response(200, json=good_workspaces_connection) 23 | ) 24 | result = runner.invoke(app, ["workspaces"]) 25 | assert "pydad" in result.stdout 26 | 27 | 28 | def test_get_all_workspaces_NetworkError(respx_mock): 29 | respx_mock.get(f"{baseUrl}workspaces").mock(side_effect=httpx.ConnectError) 30 | result = runner.invoke(app, ["workspaces"]) 31 | assert "Error in connecting to Geoserver" in result.stdout 32 | 33 | 34 | # Test - get_workspace 35 | def test_get_workspace_validation(bad_workspace_connection, respx_mock): 36 | respx_mock.get(f"{baseUrl}workspaces/sfsf").mock( 37 | return_value=httpx.Response(404, json=bad_workspace_connection) 38 | ) 39 | result = runner.invoke(app, ["workspace", "--workspace", "sfsf"]) 40 | assert "Result not found" in result.stdout 41 | 42 | 43 | def test_get_workspace_success(good_workspace_connection, respx_mock): 44 | respx_mock.get(f"{baseUrl}workspaces/pydad").mock( 45 | return_value=httpx.Response(200, json=good_workspace_connection) 46 | ) 47 | result = runner.invoke(app, ["workspace", "--workspace", "pydad"]) 48 | assert "pydad" in result.stdout 49 | 50 | 51 | def test_get_workspace_ConnectError(respx_mock): 52 | respx_mock.get(f"{baseUrl}workspaces/pydad").mock(side_effect=httpx.ConnectError) 53 | result = runner.invoke(app, ["workspace", "--workspace", "pydad"]) 54 | assert "Error in connecting to Geoserver" in result.stdout 55 | 56 | 57 | # Test - get_vector_stores_in_workspaces 58 | def test_get_vector_stores_in_workspaces_validation( 59 | invalid_datastores_model_connection, respx_mock 60 | ): 61 | respx_mock.get(f"{baseUrl}workspaces/sfsf/datastores").mock( 62 | return_value=httpx.Response(404, json=invalid_datastores_model_connection) 63 | ) 64 | result = runner.invoke(app, ["vector-st-wp", "--workspace", "sfsf"]) 65 | assert "Result not found" in result.stdout 66 | 67 | 68 | def test_get_vector_stores_in_workspaces_success( 69 | good_datastores_model_connection, respx_mock 70 | ): 71 | respx_mock.get(f"{baseUrl}workspaces/sfsf/datastores").mock( 72 | return_value=httpx.Response(200, json=good_datastores_model_connection) 73 | ) 74 | result = runner.invoke(app, ["vector-st-wp", "--workspace", "sfsf"]) 75 | assert "jumper" in result.stdout 76 | 77 | 78 | def test_get_vector_stores_in_workspaces_ConnectError(respx_mock): 79 | respx_mock.get(f"{baseUrl}workspaces/sfsf/datastores").mock( 80 | side_effect=httpx.ConnectError 81 | ) 82 | result = runner.invoke(app, ["vector-st-wp", "--workspace", "sfsf"]) 83 | assert "Error in connecting to Geoserver" in result.stdout 84 | 85 | 86 | # Test - get_raster_stores_in_workspaces 87 | def test_get_raster_stores_in_workspaces_validation( 88 | invalid_coverages_stores_model_connection, respx_mock 89 | ): 90 | respx_mock.get(f"{baseUrl}workspaces/sfsf/coveragestores").mock( 91 | return_value=httpx.Response(404, json=invalid_coverages_stores_model_connection) 92 | ) 93 | result = runner.invoke(app, ["raster-st-wp", "--workspace", "sfsf"]) 94 | assert "Result not found" in result.stdout 95 | 96 | 97 | def test_get_raster_stores_in_workspaces_success( 98 | good_coverages_stores_model_connection, respx_mock 99 | ): 100 | respx_mock.get(f"{baseUrl}workspaces/sfsf/coveragestores").mock( 101 | return_value=httpx.Response(200, json=good_coverages_stores_model_connection) 102 | ) 103 | result = runner.invoke(app, ["raster-st-wp", "--workspace", "sfsf"]) 104 | assert "RGB_125" in result.stdout 105 | 106 | 107 | def test_get_raster_stores_in_workspaces_ConnectError(respx_mock): 108 | respx_mock.get(f"{baseUrl}workspaces/sfsf/coveragestores").mock( 109 | side_effect=httpx.ConnectError 110 | ) 111 | result = runner.invoke(app, ["raster-st-wp", "--workspace", "sfsf"]) 112 | assert "Error in connecting to Geoserver" in result.stdout 113 | 114 | 115 | # Test - get_vector_store 116 | def test_get_vector_store_validation(invalid_datastore_model_connection, respx_mock): 117 | respx_mock.get(f"{baseUrl}workspaces/sfsf/datastores/jumper.json").mock( 118 | return_value=httpx.Response(404, json=invalid_datastore_model_connection) 119 | ) 120 | result = runner.invoke( 121 | app, ["vector-store", "--workspace", "sfsf", "--store", "jumper"] 122 | ) 123 | assert "Result not found" in result.stdout 124 | 125 | 126 | def test_get_vector_store_success(good_datastore_model_connection, respx_mock): 127 | respx_mock.get(f"{baseUrl}workspaces/sfsf/datastores/jumper.json").mock( 128 | return_value=httpx.Response(200, json=good_datastore_model_connection) 129 | ) 130 | result = runner.invoke( 131 | app, ["vector-store", "--workspace", "sfsf", "--store", "jumper"] 132 | ) 133 | assert "jumper" in result.stdout 134 | 135 | 136 | def test_get_vector_store_ConnectError(respx_mock): 137 | respx_mock.get(f"{baseUrl}workspaces/sfsf/datastores/jumper.json").mock( 138 | side_effect=httpx.ConnectError 139 | ) 140 | result = runner.invoke( 141 | app, ["vector-store", "--workspace", "sfsf", "--store", "jumper"] 142 | ) 143 | assert "Error in connecting to Geoserver" in result.stdout 144 | 145 | 146 | # Test - get_raster_store 147 | def test_get_raster_store_validation( 148 | invalid_coverages_store_model_connection, respx_mock 149 | ): 150 | respx_mock.get(f"{baseUrl}workspaces/cite/coveragestores/RGB_125.json").mock( 151 | return_value=httpx.Response(404, json=invalid_coverages_store_model_connection) 152 | ) 153 | result = runner.invoke( 154 | app, ["raster-store", "--workspace", "cite", "--store", "RGB_125"] 155 | ) 156 | assert "Result not found" in result.stdout 157 | 158 | 159 | def test_get_raster_store_success(good_coverages_store_model_connection, respx_mock): 160 | respx_mock.get(f"{baseUrl}workspaces/cite/coveragestores/RGB_125.json").mock( 161 | return_value=httpx.Response(200, json=good_coverages_store_model_connection) 162 | ) 163 | result = runner.invoke( 164 | app, ["raster-store", "--workspace", "cite", "--store", "RGB_125"] 165 | ) 166 | assert "RGB_125" in result.stdout 167 | 168 | 169 | def test_get_raster_store_ConnectError(respx_mock): 170 | respx_mock.get(f"{baseUrl}workspaces/cite/coveragestores/RGB_125.json").mock( 171 | side_effect=httpx.ConnectError 172 | ) 173 | result = runner.invoke( 174 | app, ["raster-store", "--workspace", "cite", "--store", "RGB_125"] 175 | ) 176 | assert "Error in connecting to Geoserver" in result.stdout 177 | 178 | 179 | # Test - get_all_styles 180 | def test_get_all_styles_validation(invalid_all_styles_model_connection, respx_mock): 181 | respx_mock.get(f"{baseUrl}styles").mock( 182 | return_value=httpx.Response(404, json=invalid_all_styles_model_connection) 183 | ) 184 | result = runner.invoke(app, ["styles"]) 185 | assert "Result not found" in result.stdout 186 | 187 | 188 | def test_get_all_styles_success(good_all_styles_model_connection, respx_mock): 189 | respx_mock.get(f"{baseUrl}styles").mock( 190 | return_value=httpx.Response(200, json=good_all_styles_model_connection) 191 | ) 192 | result = runner.invoke(app, ["styles"]) 193 | assert "CUSD 2020 Census" in result.stdout 194 | 195 | 196 | def test_get_all_styles_ConnectError(respx_mock): 197 | respx_mock.get(f"{baseUrl}styles").mock(side_effect=httpx.ConnectError) 198 | result = runner.invoke(app, ["styles"]) 199 | assert "Error in connecting to Geoserver" in result.stdout 200 | 201 | 202 | # Test - get_style 203 | def test_get_style_validation(invalid_style_model_connection, respx_mock): 204 | respx_mock.get(f"{baseUrl}styles/burg.json").mock( 205 | return_value=httpx.Response(404, json=invalid_style_model_connection) 206 | ) 207 | result = runner.invoke(app, ["style", "--style", "burg"]) 208 | assert "Result not found" in result.stdout 209 | 210 | 211 | def test_get_style_success(good_style_model_connection, respx_mock): 212 | respx_mock.get(f"{baseUrl}styles/burg.json").mock( 213 | return_value=httpx.Response(200, json=good_style_model_connection) 214 | ) 215 | result = runner.invoke(app, ["style", "--style", "burg"]) 216 | assert "burg" in result.stdout 217 | 218 | 219 | def test_get_style_ConnectError(respx_mock): 220 | respx_mock.get(f"{baseUrl}styles/burg.json").mock(side_effect=httpx.ConnectError) 221 | result = runner.invoke(app, ["style", "--style", "burg"]) 222 | assert "Error in connecting to Geoserver" in result.stdout 223 | 224 | 225 | # Test - create_workspace 226 | def test_create_workspace_validation(invalid_new_workspace_connection, respx_mock): 227 | respx_mock.post(f"{baseUrl}workspaces?default=False").mock( 228 | return_value=httpx.Response(404, json=invalid_new_workspace_connection) 229 | ) 230 | result = runner.invoke( 231 | app, 232 | ["create-workspace", "--workspace", "burg", "--no-default", "--no-isolated"], 233 | ) 234 | assert "Result not found" in result.stdout 235 | 236 | 237 | def test_create_workspace_success(good_new_workspace_connection, respx_mock): 238 | respx_mock.post(f"{baseUrl}workspaces?default=False").mock( 239 | return_value=httpx.Response(201, json=good_new_workspace_connection) 240 | ) 241 | result = runner.invoke( 242 | app, ["create-workspace", "--workspace", "pydad", "--no-default", "--isolated"] 243 | ) 244 | assert "Data added successfully" in result.stdout 245 | 246 | 247 | def test_create_workspace_ConnectError(respx_mock): 248 | respx_mock.post(f"{baseUrl}workspaces?default=False").mock( 249 | side_effect=httpx.ConnectError 250 | ) 251 | result = runner.invoke( 252 | app, ["create-workspace", "--workspace", "pydad", "--no-default", "--isolated"] 253 | ) 254 | assert "Error in connecting to Geoserver" in result.stdout 255 | 256 | 257 | # Test - pg_store 258 | def test_pg_store_validation(invalid_new_pg_store_connection, respx_mock): 259 | respx_mock.post(f"{baseUrl}workspaces/cesium/datastores/").mock( 260 | return_value=httpx.Response(404, json=invalid_new_pg_store_connection) 261 | ) 262 | result = runner.invoke( 263 | app, 264 | [ 265 | "create-pg-store", 266 | "--name", 267 | "pgg", 268 | "--workspace", 269 | "cesium", 270 | "--dbname", 271 | "postgres", 272 | "--dbpwd", 273 | "postgres", 274 | ], 275 | ) 276 | assert "Result not found" in result.stdout 277 | 278 | 279 | def test_pg_store_success(good_new_workspace_connection, respx_mock): 280 | respx_mock.post(f"{baseUrl}workspaces/cesium/datastores/").mock( 281 | return_value=httpx.Response(201, json=good_new_workspace_connection) 282 | ) 283 | result = runner.invoke( 284 | app, 285 | [ 286 | "create-pg-store", 287 | "--name", 288 | "pgg", 289 | "--workspace", 290 | "cesium", 291 | "--dbname", 292 | "postgres", 293 | "--dbpwd", 294 | "postgres", 295 | ], 296 | ) 297 | assert "Data added successfully" in result.stdout 298 | 299 | 300 | def test_pg_store_ConnectError(respx_mock): 301 | respx_mock.post(f"{baseUrl}workspaces/cesium/datastores/").mock( 302 | side_effect=httpx.ConnectError 303 | ) 304 | result = runner.invoke( 305 | app, 306 | [ 307 | "create-pg-store", 308 | "--name", 309 | "pgg", 310 | "--workspace", 311 | "cesium", 312 | "--dbname", 313 | "postgres", 314 | "--dbpwd", 315 | "postgres", 316 | ], 317 | ) 318 | assert "Error in connecting to Geoserver" in result.stdout 319 | 320 | 321 | # Test - get_all_layers 322 | def test_get_all_layers_validation(bad_layers_connection, respx_mock): 323 | respx_mock.get(f"{baseUrl}layers").mock( 324 | return_value=httpx.Response(404, json=bad_layers_connection) 325 | ) 326 | result = runner.invoke(app, ["layers"]) 327 | assert "404" in result.stdout 328 | 329 | 330 | def test_get_all_layers_success(good_layers_connection, respx_mock): 331 | respx_mock.get(f"{baseUrl}layers").mock( 332 | return_value=httpx.Response(200, json=good_layers_connection) 333 | ) 334 | result = runner.invoke(app, ["layers"]) 335 | assert "tiger:giant_polygon" in result.stdout 336 | 337 | 338 | def test_get_all_layers_NetworkError(respx_mock): 339 | respx_mock.get(f"{baseUrl}layers").mock(side_effect=httpx.ConnectError) 340 | result = runner.invoke(app, ["layers"]) 341 | assert "Error in connecting to Geoserver" in result.stdout 342 | 343 | 344 | # Test - get_layer 345 | def test_get_layer_validation(bad_layer_connection, respx_mock): 346 | respx_mock.get(f"{baseUrl}layers/tiger:poi").mock( 347 | return_value=httpx.Response(404, json=bad_layer_connection) 348 | ) 349 | result = runner.invoke(app, ["layer", "--workspace", "tiger", "--layer", "poi"]) 350 | assert "404" in result.stdout 351 | 352 | 353 | def test_get_layer_success(good_layer_connection, respx_mock): 354 | respx_mock.get(f"{baseUrl}layers/tiger:poi").mock( 355 | return_value=httpx.Response(200, json=good_layer_connection) 356 | ) 357 | result = runner.invoke(app, ["layer", "--workspace", "tiger", "--layer", "poi"]) 358 | assert "poi" in result.stdout 359 | 360 | 361 | def test_get_layer_NetworkError(respx_mock): 362 | respx_mock.get(f"{baseUrl}layers/tiger:poi").mock(side_effect=httpx.ConnectError) 363 | result = runner.invoke(app, ["layer", "--workspace", "tiger", "--layer", "poi"]) 364 | assert "Error in connecting to Geoserver" in result.stdout 365 | 366 | 367 | # Test - get_all_layer_groups 368 | def test_get_all_layer_groups_validation(bad_layer_groups_connection, respx_mock): 369 | respx_mock.get(f"{baseUrl}workspaces/ne/layergroups").mock( 370 | return_value=httpx.Response(404, json=bad_layer_groups_connection) 371 | ) 372 | result = runner.invoke(app, ["layer-groups", "--workspace", "ne"]) 373 | assert "404" in result.stdout 374 | 375 | 376 | def test_get_all_layer_groups_success(good_layer_groups_connection, respx_mock): 377 | respx_mock.get(f"{baseUrl}workspaces/ne/layergroups").mock( 378 | return_value=httpx.Response(200, json=good_layer_groups_connection) 379 | ) 380 | result = runner.invoke(app, ["layer-groups", "--workspace", "ne"]) 381 | assert "tg" in result.stdout 382 | 383 | 384 | def test_get_all_layer_groups_NetworkError(respx_mock): 385 | respx_mock.get(f"{baseUrl}workspaces/ne/layergroups").mock( 386 | side_effect=httpx.ConnectError 387 | ) 388 | result = runner.invoke(app, ["layer-groups", "--workspace", "ne"]) 389 | assert "Error in connecting to Geoserver" in result.stdout 390 | 391 | 392 | # Test - all_geofence_rules 393 | def test_all_geofence_rules_validation(bad_all_geofence_rules_connection, respx_mock): 394 | respx_mock.get(f"{baseUrl}about/status.json").mock( 395 | return_value=httpx.Response( 396 | 200, json={"statuss": {"status": [{"name": "geofence"}]}} 397 | ) 398 | ) 399 | respx_mock.get( 400 | f"{baseUrl}geofence/rules/", headers={"Accept": "application/json"} 401 | ).mock(return_value=httpx.Response(404, json=bad_all_geofence_rules_connection)) 402 | result = runner.invoke(app, ["geofence-rules"]) 403 | assert "404" in result.stdout 404 | 405 | 406 | def test_all_geofence_rules_success(good_all_geofence_rules_connection, respx_mock): 407 | respx_mock.get(f"{baseUrl}about/status.json").mock( 408 | return_value=httpx.Response( 409 | 200, json={"statuss": {"status": [{"name": "geofence"}]}} 410 | ) 411 | ) 412 | respx_mock.get( 413 | f"{baseUrl}geofence/rules/", headers={"Accept": "application/json"} 414 | ).mock(return_value=httpx.Response(200, json=good_all_geofence_rules_connection)) 415 | result = runner.invoke(app, ["geofence-rules"]) 416 | assert "2" in result.stdout 417 | 418 | 419 | def test_all_geofence_rules_NetworkError(respx_mock): 420 | respx_mock.get(f"{baseUrl}about/status.json").mock( 421 | return_value=httpx.Response( 422 | 200, json={"statuss": {"status": [{"name": "geofence"}]}} 423 | ) 424 | ) 425 | respx_mock.get( 426 | f"{baseUrl}geofence/rules/", headers={"Accept": "application/json"} 427 | ).mock(side_effect=httpx.ConnectError) 428 | result = runner.invoke(app, ["geofence-rules"]) 429 | assert "Error in connecting to Geoserver" in result.stdout 430 | -------------------------------------------------------------------------------- /tests/test_models.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from pydantic import ValidationError 3 | 4 | from geoserverx.models.coverages_store import ( 5 | CoveragesStoreInBulk, 6 | CoveragesStoreModel, 7 | CoveragesStoresDict, 8 | CoveragesStoresModel, 9 | ) 10 | from geoserverx.models.data_store import ( 11 | DatastoreConnection, 12 | DataStoreDict, 13 | DataStoreInBulk, 14 | DatastoreItem, 15 | DataStoreModel, 16 | DataStoresModel, 17 | EntryItem, 18 | ) 19 | from geoserverx.models.geofence import RulesResponse 20 | from geoserverx.models.layer_group import LayerGroupsModel 21 | from geoserverx.models.style import ( 22 | AllStylesModel, 23 | SingleStyle, 24 | StyleModel, 25 | allStyle, 26 | allStyleList, 27 | ) 28 | from geoserverx.models.workspace import ( 29 | NewWorkspace, 30 | WorkspaceInBulk, 31 | WorkspaceModel, 32 | WorkspacesModel, 33 | workspaceDict, 34 | ) 35 | 36 | 37 | # Testing DataStoreInBulk 38 | def test_datastoreinbulk_connection(good_datastore_in_bulk_connection): 39 | ds_connection = DataStoreInBulk(**good_datastore_in_bulk_connection) 40 | assert ds_connection.name == "just" 41 | assert ds_connection.href == "https://www.linkedin.com/notifications/" 42 | 43 | 44 | def test_datastoreinbulk_failure(bad_datastore_in_bulk_connection): 45 | with pytest.raises(ValidationError): 46 | DataStoreInBulk(**bad_datastore_in_bulk_connection) 47 | 48 | 49 | # Testing DataStoreDict 50 | def test_datastoredict_connection(good_datastore_dict_connection): 51 | ds_connection = DataStoreDict(**good_datastore_dict_connection) 52 | assert ds_connection.dataStore[0].name == "just" 53 | 54 | 55 | def test_datastoredict_failure(bad_datastore_dict_connection): 56 | with pytest.raises(ValidationError): 57 | DataStoreDict(**bad_datastore_dict_connection) 58 | 59 | 60 | # Testing DataStoresModel 61 | def test_datastoresmodel_connection(good_datastores_model_connection): 62 | ds_connection = DataStoresModel(**good_datastores_model_connection) 63 | assert ds_connection.dataStores.dataStore[0].name == "jumper" 64 | 65 | 66 | def test_datastoresmodel_failure(bad_datastores_model_connection): 67 | with pytest.raises(ValidationError): 68 | DataStoresModel(**bad_datastores_model_connection) 69 | 70 | 71 | # Testing DatastoreConnection 72 | def test_datastoreconnection_connection(good_datastore_connection_connection): 73 | ds_connection = DatastoreConnection(**good_datastore_connection_connection) 74 | assert ds_connection.key == "just" 75 | 76 | 77 | def test_datastoreconnection_failure(bad_datastore_connection_connection): 78 | with pytest.raises(ValidationError): 79 | DatastoreConnection(**bad_datastore_connection_connection) 80 | 81 | 82 | # Testing EntryItem 83 | def test_entryitem_connection(good_entry_item_connection): 84 | ds_connection = EntryItem(**good_entry_item_connection) 85 | assert ds_connection.entry[0].key == "just" 86 | 87 | 88 | def test_entryitem_failure(bad_entry_item_connection): 89 | with pytest.raises(ValidationError): 90 | EntryItem(**bad_entry_item_connection) 91 | 92 | 93 | # Testing DatastoreItem 94 | def test_datastoreitem_connection(good_datastore_item_connection): 95 | ds_connection = DatastoreItem(**good_datastore_item_connection) 96 | assert ds_connection.connectionParameters.entry[0].key == "just" 97 | 98 | 99 | def test_datastoreitem_failure(bad_datastore_item_connection): 100 | with pytest.raises(ValidationError): 101 | DatastoreItem(**bad_datastore_item_connection) 102 | 103 | 104 | # Testing DataStoreModel 105 | def test_datastoremodel_connection(good_datastore_model_connection): 106 | ds_connection = DataStoreModel(**good_datastore_model_connection) 107 | assert ds_connection.dataStore.name == "jumper" 108 | 109 | 110 | def test_datastoremodel_failure(bad_datastore_model_connection): 111 | with pytest.raises(ValidationError): 112 | DataStoreModel(**bad_datastore_model_connection) 113 | 114 | 115 | # Testing CoveragesStoreInBulk 116 | def test_coveragesstoreinbulk_connection(good_coverages_store_in_bulk_connection): 117 | ds_connection = CoveragesStoreInBulk(**good_coverages_store_in_bulk_connection) 118 | assert ds_connection.name == "just" 119 | assert ds_connection.href == "https://www.linkedin.com/notifications/" 120 | 121 | 122 | def test_coveragesstoreinbulk_failure(bad_coverages_store_in_bulk_connection): 123 | with pytest.raises(ValidationError): 124 | CoveragesStoreInBulk(**bad_coverages_store_in_bulk_connection) 125 | 126 | 127 | # Testing CoveragesStoresDict 128 | def test_coveragesstoresdict_connection(good_coverages_stores_dict_connection): 129 | ds_connection = CoveragesStoresDict(**good_coverages_stores_dict_connection) 130 | assert ds_connection.coverageStore[0].name == "just" 131 | 132 | 133 | def test_coveragesstoresdict_failure(bad_coverages_stores_dict_connection): 134 | with pytest.raises(ValidationError): 135 | CoveragesStoresDict(**bad_coverages_stores_dict_connection) 136 | 137 | 138 | # Testing CoveragesStoresModel 139 | def test_coveragesstoresmodel_connection(good_coverages_stores_model_connection): 140 | ds_connection = CoveragesStoresModel(**good_coverages_stores_model_connection) 141 | assert ds_connection.coverageStores.coverageStore[0].name == "RGB_125" 142 | 143 | 144 | def test_coveragesstoresmodel_failure(bad_coverages_stores_model_connection): 145 | with pytest.raises(ValidationError): 146 | CoveragesStoresModel(**bad_coverages_stores_model_connection) 147 | 148 | 149 | # Testing CoveragesStoreModel 150 | def test_coveragesstoremodel_connection(good_coverages_store_model_connection): 151 | ds_connection = CoveragesStoreModel(**good_coverages_store_model_connection) 152 | assert ds_connection.coverageStore.name == "RGB_125" 153 | 154 | 155 | def test_coveragesstoremodel_failure(bad_coverages_store_model_connection): 156 | with pytest.raises(ValidationError): 157 | CoveragesStoreModel(**bad_coverages_store_model_connection) 158 | 159 | 160 | # Testing SingleStyle 161 | def test_singlestyle_connection(good_single_style_dict_connection): 162 | ds_connection = SingleStyle(**good_single_style_dict_connection) 163 | assert ds_connection.name == "burg" 164 | 165 | 166 | def test_singlestyle_failure(bad_single_style_dict_connection): 167 | with pytest.raises(ValidationError): 168 | SingleStyle(**bad_single_style_dict_connection) 169 | 170 | 171 | # Testing StyleModel 172 | def test_stylemodel_connection(good_style_model_connection): 173 | ds_connection = StyleModel(**good_style_model_connection) 174 | assert ds_connection.style.name == "burg" 175 | 176 | 177 | def test_stylemodel_failure(bad_style_model_connection): 178 | with pytest.raises(ValidationError): 179 | StyleModel(**bad_style_model_connection) 180 | 181 | 182 | # Testing allStyleList 183 | def test_allstylelist_connection(good_all_style_list_connection): 184 | ds_connection = allStyleList(**good_all_style_list_connection) 185 | assert ds_connection.name == "CUSD 2020 Census Blocks" 186 | 187 | 188 | def test_allstylelist_failure(bad_all_style_list_connection): 189 | with pytest.raises(ValidationError): 190 | allStyleList(**bad_all_style_list_connection) 191 | 192 | 193 | # Testing allStyle 194 | def test_allstyle_connection(good_all_style_dict_connection): 195 | ds_connection = allStyle(**good_all_style_dict_connection) 196 | assert ds_connection.style[0].name == "CUSD 2020 Census Blocks" 197 | 198 | 199 | def test_allstyle_failure(bad_all_style_dict_connection): 200 | with pytest.raises(ValidationError): 201 | allStyle(**bad_all_style_dict_connection) 202 | 203 | 204 | # Testing AllStylesModel 205 | def test_allstylesmodel_connection(good_all_styles_model_connection): 206 | ds_connection = AllStylesModel(**good_all_styles_model_connection) 207 | assert ds_connection.styles.style[0].name == "CUSD 2020 Census Blocks" 208 | 209 | 210 | def test_allstylesmodel_failure(bad_all_styles_model_connection): 211 | with pytest.raises(ValidationError): 212 | AllStylesModel(**bad_all_styles_model_connection) 213 | 214 | 215 | # Testing WorkspaceInBulk 216 | def test_workspaceinbulk_connection(good_workspace_in_bulk_connection): 217 | ds_connection = WorkspaceInBulk(**good_workspace_in_bulk_connection) 218 | assert ds_connection.name == "pydad" 219 | 220 | 221 | def test_workspaceinbulk_failure(bad_workspace_in_bulk_connection): 222 | with pytest.raises(ValidationError): 223 | WorkspaceInBulk(**bad_workspace_in_bulk_connection) 224 | 225 | 226 | # Testing workspaceDict 227 | def test_workspacedict_connection(good_workspace_dict_connection): 228 | ds_connection = workspaceDict(**good_workspace_dict_connection) 229 | assert ds_connection.workspace[0].name == "pydad" 230 | 231 | 232 | def test_workspacedict_failure(bad_workspace_dict_connection): 233 | with pytest.raises(ValidationError): 234 | workspaceDict(**bad_workspace_dict_connection) 235 | 236 | 237 | # Testing WorkspacesModel 238 | def test_workspacesmodel_connection(good_workspaces_model_connection): 239 | ds_connection = WorkspacesModel(**good_workspaces_model_connection) 240 | assert ds_connection.workspaces.workspace[0].name == "pydad" 241 | 242 | 243 | def test_workspacesmodel_failure(bad_workspaces_model_connection): 244 | with pytest.raises(ValidationError): 245 | WorkspacesModel(**bad_workspaces_model_connection) 246 | 247 | 248 | # Testing WorkspaceModel 249 | def test_workspacemodel_connection(good_workspace_model_connection): 250 | ds_connection = WorkspaceModel(**good_workspace_model_connection) 251 | assert ds_connection.workspace.name == "pydad" 252 | 253 | 254 | def test_workspacemodel_failure(bad_workspace_model_connection): 255 | with pytest.raises(ValidationError): 256 | WorkspaceModel(**bad_workspace_model_connection) 257 | 258 | 259 | # Testing NewWorkspace 260 | def test_newworkspace_connection(good_new_workspace_connection): 261 | ds_connection = NewWorkspace(**good_new_workspace_connection) 262 | assert ds_connection.workspace.name == "pydad" 263 | 264 | 265 | def test_newworkspace_failure(bad_new_workspace_connection): 266 | with pytest.raises(ValidationError): 267 | NewWorkspace(**bad_new_workspace_connection) 268 | 269 | 270 | # Testing LayerGroupsModel 271 | def test_layergroupsmodel_connection(good_layer_groups_connection): 272 | ds_connection = LayerGroupsModel(**good_layer_groups_connection) 273 | assert ds_connection.layerGroups.layerGroup[0].name == "tg" 274 | 275 | 276 | def test_layergroupsmodel_failure(bad_layer_groups_connection): 277 | with pytest.raises(ValidationError): 278 | LayerGroupsModel(**bad_layer_groups_connection) 279 | 280 | 281 | # Testing LayerGroupsModel 282 | def test_RulesResponse_connection(good_all_geofence_rules_connection): 283 | ds_connection = RulesResponse(**good_all_geofence_rules_connection) 284 | assert ds_connection.count == 2 285 | 286 | 287 | def test_RulesResponse_failure(bad_all_geofence_rules_connection): 288 | with pytest.raises(ValidationError): 289 | RulesResponse(**bad_all_geofence_rules_connection) 290 | --------------------------------------------------------------------------------