geodeploy 1.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- geodeploy-1.3.0/LICENSE +202 -0
- geodeploy-1.3.0/MANIFEST.in +6 -0
- geodeploy-1.3.0/NOTICE +11 -0
- geodeploy-1.3.0/PKG-INFO +102 -0
- geodeploy-1.3.0/PYPI.md +68 -0
- geodeploy-1.3.0/README.md +196 -0
- geodeploy-1.3.0/geodeploy/__init__.py +52 -0
- geodeploy-1.3.0/geodeploy/__main__.py +13 -0
- geodeploy-1.3.0/geodeploy/admin.py +191 -0
- geodeploy-1.3.0/geodeploy/catalog.py +96 -0
- geodeploy-1.3.0/geodeploy/cli/__init__.py +7 -0
- geodeploy-1.3.0/geodeploy/cli/commands/__init__.py +2 -0
- geodeploy-1.3.0/geodeploy/cli/commands/_common.py +233 -0
- geodeploy-1.3.0/geodeploy/cli/commands/admin.py +317 -0
- geodeploy-1.3.0/geodeploy/cli/commands/auth.py +280 -0
- geodeploy-1.3.0/geodeploy/cli/commands/browse.py +224 -0
- geodeploy-1.3.0/geodeploy/cli/commands/catalog.py +107 -0
- geodeploy-1.3.0/geodeploy/cli/commands/imports.py +125 -0
- geodeploy-1.3.0/geodeploy/cli/commands/jobs.py +38 -0
- geodeploy-1.3.0/geodeploy/cli/commands/layers.py +536 -0
- geodeploy-1.3.0/geodeploy/cli/commands/portals.py +515 -0
- geodeploy-1.3.0/geodeploy/cli/commands/sources.py +97 -0
- geodeploy-1.3.0/geodeploy/cli/commands/upload.py +154 -0
- geodeploy-1.3.0/geodeploy/cli/main.py +263 -0
- geodeploy-1.3.0/geodeploy/cli/output.py +320 -0
- geodeploy-1.3.0/geodeploy/client.py +327 -0
- geodeploy-1.3.0/geodeploy/config.py +438 -0
- geodeploy-1.3.0/geodeploy/errors.py +93 -0
- geodeploy-1.3.0/geodeploy/imports.py +65 -0
- geodeploy-1.3.0/geodeploy/jobs.py +73 -0
- geodeploy-1.3.0/geodeploy/layers.py +433 -0
- geodeploy-1.3.0/geodeploy/portals.py +280 -0
- geodeploy-1.3.0/geodeploy/py.typed +0 -0
- geodeploy-1.3.0/geodeploy/sources.py +70 -0
- geodeploy-1.3.0/geodeploy/styles.py +467 -0
- geodeploy-1.3.0/geodeploy/transport.py +355 -0
- geodeploy-1.3.0/geodeploy/uploads.py +474 -0
- geodeploy-1.3.0/geodeploy.egg-info/PKG-INFO +102 -0
- geodeploy-1.3.0/geodeploy.egg-info/SOURCES.txt +51 -0
- geodeploy-1.3.0/geodeploy.egg-info/dependency_links.txt +1 -0
- geodeploy-1.3.0/geodeploy.egg-info/entry_points.txt +2 -0
- geodeploy-1.3.0/geodeploy.egg-info/requires.txt +3 -0
- geodeploy-1.3.0/geodeploy.egg-info/top_level.txt +1 -0
- geodeploy-1.3.0/pyproject.toml +80 -0
- geodeploy-1.3.0/setup.cfg +4 -0
- geodeploy-1.3.0/tests/conftest.py +686 -0
- geodeploy-1.3.0/tests/test_cli.py +640 -0
- geodeploy-1.3.0/tests/test_config.py +222 -0
- geodeploy-1.3.0/tests/test_layers_portals.py +218 -0
- geodeploy-1.3.0/tests/test_output.py +178 -0
- geodeploy-1.3.0/tests/test_styles_jobs.py +285 -0
- geodeploy-1.3.0/tests/test_transport.py +216 -0
- geodeploy-1.3.0/tests/test_uploads.py +299 -0
geodeploy-1.3.0/LICENSE
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
|
|
2
|
+
Apache License
|
|
3
|
+
Version 2.0, January 2004
|
|
4
|
+
http://www.apache.org/licenses/
|
|
5
|
+
|
|
6
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
7
|
+
|
|
8
|
+
1. Definitions.
|
|
9
|
+
|
|
10
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
11
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
12
|
+
|
|
13
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
14
|
+
the copyright owner that is granting the License.
|
|
15
|
+
|
|
16
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
17
|
+
other entities that control, are controlled by, or are under common
|
|
18
|
+
control with that entity. For the purposes of this definition,
|
|
19
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
20
|
+
direction or management of such entity, whether by contract or
|
|
21
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
22
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
23
|
+
|
|
24
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
25
|
+
exercising permissions granted by this License.
|
|
26
|
+
|
|
27
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
28
|
+
including but not limited to software source code, documentation
|
|
29
|
+
source, and configuration files.
|
|
30
|
+
|
|
31
|
+
"Object" form shall mean any form resulting from mechanical
|
|
32
|
+
transformation or translation of a Source form, including but
|
|
33
|
+
not limited to compiled object code, generated documentation,
|
|
34
|
+
and conversions to other media types.
|
|
35
|
+
|
|
36
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
37
|
+
Object form, made available under the License, as indicated by a
|
|
38
|
+
copyright notice that is included in or attached to the work
|
|
39
|
+
(an example is provided in the Appendix below).
|
|
40
|
+
|
|
41
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
42
|
+
form, that is based on (or derived from) the Work and for which the
|
|
43
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
44
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
45
|
+
of this License, Derivative Works shall not include works that remain
|
|
46
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
47
|
+
the Work and Derivative Works thereof.
|
|
48
|
+
|
|
49
|
+
"Contribution" shall mean any work of authorship, including
|
|
50
|
+
the original version of the Work and any modifications or additions
|
|
51
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
52
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
53
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
54
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
55
|
+
means any form of electronic, verbal, or written communication sent
|
|
56
|
+
to the Licensor or its representatives, including but not limited to
|
|
57
|
+
communication on electronic mailing lists, source code control systems,
|
|
58
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
59
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
60
|
+
excluding communication that is conspicuously marked or otherwise
|
|
61
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
62
|
+
|
|
63
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
64
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
65
|
+
subsequently incorporated within the Work.
|
|
66
|
+
|
|
67
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
68
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
69
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
70
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
71
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
72
|
+
Work and such Derivative Works in Source or Object form.
|
|
73
|
+
|
|
74
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
75
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
76
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
77
|
+
(except as stated in this section) patent license to make, have made,
|
|
78
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
79
|
+
where such license applies only to those patent claims licensable
|
|
80
|
+
by such Contributor that are necessarily infringed by their
|
|
81
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
82
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
83
|
+
institute patent litigation against any entity (including a
|
|
84
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
85
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
86
|
+
or contributory patent infringement, then any patent licenses
|
|
87
|
+
granted to You under this License for that Work shall terminate
|
|
88
|
+
as of the date such litigation is filed.
|
|
89
|
+
|
|
90
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
91
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
92
|
+
modifications, and in Source or Object form, provided that You
|
|
93
|
+
meet the following conditions:
|
|
94
|
+
|
|
95
|
+
(a) You must give any other recipients of the Work or
|
|
96
|
+
Derivative Works a copy of this License; and
|
|
97
|
+
|
|
98
|
+
(b) You must cause any modified files to carry prominent notices
|
|
99
|
+
stating that You changed the files; and
|
|
100
|
+
|
|
101
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
102
|
+
that You distribute, all copyright, patent, trademark, and
|
|
103
|
+
attribution notices from the Source form of the Work,
|
|
104
|
+
excluding those notices that do not pertain to any part of
|
|
105
|
+
the Derivative Works; and
|
|
106
|
+
|
|
107
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
108
|
+
distribution, then any Derivative Works that You distribute must
|
|
109
|
+
include a readable copy of the attribution notices contained
|
|
110
|
+
within such NOTICE file, excluding those notices that do not
|
|
111
|
+
pertain to any part of the Derivative Works, in at least one
|
|
112
|
+
of the following places: within a NOTICE text file distributed
|
|
113
|
+
as part of the Derivative Works; within the Source form or
|
|
114
|
+
documentation, if provided along with the Derivative Works; or,
|
|
115
|
+
within a display generated by the Derivative Works, if and
|
|
116
|
+
wherever such third-party notices normally appear. The contents
|
|
117
|
+
of the NOTICE file are for informational purposes only and
|
|
118
|
+
do not modify the License. You may add Your own attribution
|
|
119
|
+
notices within Derivative Works that You distribute, alongside
|
|
120
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
121
|
+
that such additional attribution notices cannot be construed
|
|
122
|
+
as modifying the License.
|
|
123
|
+
|
|
124
|
+
You may add Your own copyright statement to Your modifications and
|
|
125
|
+
may provide additional or different license terms and conditions
|
|
126
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
127
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
128
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
129
|
+
the conditions stated in this License.
|
|
130
|
+
|
|
131
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
132
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
133
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
134
|
+
this License, without any additional terms or conditions.
|
|
135
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
136
|
+
the terms of any separate license agreement you may have executed
|
|
137
|
+
with Licensor regarding such Contributions.
|
|
138
|
+
|
|
139
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
140
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
141
|
+
except as required for reasonable and customary use in describing the
|
|
142
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
143
|
+
|
|
144
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
145
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
146
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
147
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
148
|
+
implied, including, without limitation, any warranties or conditions
|
|
149
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
150
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
151
|
+
appropriateness of using or redistributing the Work and assume any
|
|
152
|
+
risks associated with Your exercise of permissions under this License.
|
|
153
|
+
|
|
154
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
155
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
156
|
+
unless required by applicable law (such as deliberate and grossly
|
|
157
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
158
|
+
liable to You for damages, including any direct, indirect, special,
|
|
159
|
+
incidental, or consequential damages of any character arising as a
|
|
160
|
+
result of this License or out of the use or inability to use the
|
|
161
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
162
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
163
|
+
other commercial damages or losses), even if such Contributor
|
|
164
|
+
has been advised of the possibility of such damages.
|
|
165
|
+
|
|
166
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
167
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
168
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
169
|
+
or other liability obligations and/or rights consistent with this
|
|
170
|
+
License. However, in accepting such obligations, You may act only
|
|
171
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
172
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
173
|
+
defend, and hold each Contributor harmless for any liability
|
|
174
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
175
|
+
of your accepting any such warranty or additional liability.
|
|
176
|
+
|
|
177
|
+
END OF TERMS AND CONDITIONS
|
|
178
|
+
|
|
179
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
180
|
+
|
|
181
|
+
To apply the Apache License to your work, attach the following
|
|
182
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
183
|
+
replaced with your own identifying information. (Don't include
|
|
184
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
185
|
+
comment syntax for the file format. We also recommend that a
|
|
186
|
+
file or class name and description of purpose be included on the
|
|
187
|
+
same "printed page" as the copyright notice for easier
|
|
188
|
+
identification within third-party archives.
|
|
189
|
+
|
|
190
|
+
Copyright 2026 Koffi Dodji Noumonvi
|
|
191
|
+
|
|
192
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
193
|
+
you may not use this file except in compliance with the License.
|
|
194
|
+
You may obtain a copy of the License at
|
|
195
|
+
|
|
196
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
197
|
+
|
|
198
|
+
Unless required by applicable law or agreed to in writing, software
|
|
199
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
200
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
201
|
+
See the License for the specific language governing permissions and
|
|
202
|
+
limitations under the License.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# The sdist should be enough to run the tests from, so a packager or a distro can verify the
|
|
2
|
+
# release rather than take the wheel on trust. LICENSE, NOTICE and PYPI.md come in automatically
|
|
3
|
+
# from the metadata; the tests do not.
|
|
4
|
+
recursive-include tests *.py
|
|
5
|
+
include README.md
|
|
6
|
+
global-exclude __pycache__/* *.py[cod]
|
geodeploy-1.3.0/NOTICE
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
GeoDeploy
|
|
2
|
+
Copyright 2026 Koffi Dodji Noumonvi
|
|
3
|
+
|
|
4
|
+
This product includes software developed for the GeoDeploy project
|
|
5
|
+
(https://github.com/bravemaster3/GeoDeploy).
|
|
6
|
+
|
|
7
|
+
Licensed under the Apache License, Version 2.0. See LICENSE.
|
|
8
|
+
|
|
9
|
+
GeoDeploy runs alongside third-party services (PostGIS, MinIO, Martin, TiTiler,
|
|
10
|
+
Redis, nginx) and bundles third-party libraries, each under its own licence.
|
|
11
|
+
Those services run as separate processes; their licences are unaffected by this one.
|
geodeploy-1.3.0/PKG-INFO
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: geodeploy
|
|
3
|
+
Version: 1.3.0
|
|
4
|
+
Summary: Command-line client and Python API for GeoDeploy — upload spatial data, build and publish portals.
|
|
5
|
+
Author: Koffi Dodji Noumonvi
|
|
6
|
+
License-Expression: Apache-2.0
|
|
7
|
+
Project-URL: Homepage, https://github.com/bravemaster3/GeoDeploy
|
|
8
|
+
Project-URL: Documentation, https://docs-geodeploy.kndev.org/cli/
|
|
9
|
+
Project-URL: Repository, https://github.com/bravemaster3/GeoDeploy
|
|
10
|
+
Project-URL: Issues, https://github.com/bravemaster3/GeoDeploy/issues
|
|
11
|
+
Project-URL: Changelog, https://github.com/bravemaster3/GeoDeploy/blob/main/CHANGELOG.md
|
|
12
|
+
Keywords: gis,geospatial,postgis,geoparquet,cog,maplibre,qgis,stac,ogc-api-features,geodeploy
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Environment :: Console
|
|
15
|
+
Classifier: Intended Audience :: Science/Research
|
|
16
|
+
Classifier: Intended Audience :: System Administrators
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
24
|
+
Classifier: Topic :: Scientific/Engineering :: GIS
|
|
25
|
+
Classifier: Topic :: Utilities
|
|
26
|
+
Classifier: Typing :: Typed
|
|
27
|
+
Requires-Python: >=3.9
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
License-File: LICENSE
|
|
30
|
+
License-File: NOTICE
|
|
31
|
+
Provides-Extra: dev
|
|
32
|
+
Requires-Dist: pytest>=7; extra == "dev"
|
|
33
|
+
Dynamic: license-file
|
|
34
|
+
|
|
35
|
+
# geodeploy
|
|
36
|
+
|
|
37
|
+
**Command-line client and Python API for [GeoDeploy](https://github.com/bravemaster3/GeoDeploy)** —
|
|
38
|
+
the self-hosted spatial data platform and geoportal builder.
|
|
39
|
+
|
|
40
|
+
Upload data, style it, build a portal and publish it, without opening a browser:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
pip install geodeploy
|
|
44
|
+
|
|
45
|
+
geodeploy login https://geodeploy.example.org --token gdp_…
|
|
46
|
+
geodeploy upload roads.gpkg sites.csv dem.tif --wait
|
|
47
|
+
geodeploy portals create "Field sites 2026" --publish
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
**No dependencies. Python 3.9+.** Every request goes through the standard library, so installing
|
|
51
|
+
this pulls in nothing else — and a QGIS plugin can vendor the same client without asking anyone to
|
|
52
|
+
pip-install into QGIS.
|
|
53
|
+
|
|
54
|
+
If your shell cannot find `geodeploy` after installing, pip's scripts directory is not on your
|
|
55
|
+
`PATH` — `python -m geodeploy` works regardless, and `pipx install geodeploy` or a virtual
|
|
56
|
+
environment avoids it.
|
|
57
|
+
|
|
58
|
+
## What it covers
|
|
59
|
+
|
|
60
|
+
- **Uploads** — shapefile, GeoPackage, GeoJSON, CSV (X/Y or WKT), GeoParquet, GeoTIFF. Many files
|
|
61
|
+
in one command; the route is chosen per file, and anything over 48 MB goes direct to object
|
|
62
|
+
storage in parallel presigned parts, so a multi-gigabyte upload survives a proxy that caps
|
|
63
|
+
request bodies.
|
|
64
|
+
- **Layers** — list, inspect, rename, share (STAC / OGC API - Features opt-in), share links per
|
|
65
|
+
tool, download, delete, restart a stalled ingest.
|
|
66
|
+
- **Symbology** — colour, opacity, outlines, dashes, markers, raster colormaps and stretches, plus
|
|
67
|
+
data-driven styling: `--color-field pop --classify jenks --classes 6 --ramp magma`, proportional
|
|
68
|
+
size, and attribute-driven 3D. Classification is computed by the instance, with the same code the
|
|
69
|
+
portal editor uses, so the CLI and the editor can never disagree about a class.
|
|
70
|
+
- **Portals** — create (web map, story map or catalog), arrange and style layers, folders, About
|
|
71
|
+
page, assets, access tiers, publish, and a whole-configuration JSON round trip for version
|
|
72
|
+
control.
|
|
73
|
+
- **Everything else** — external WMS/XYZ/WFS services, registering data already in PostGIS or the
|
|
74
|
+
bucket, the public STAC and OGC API - Features catalog, ingest jobs, users, and instance
|
|
75
|
+
administration (health, services, updates, backups, activity log).
|
|
76
|
+
|
|
77
|
+
Every command takes `--json`, where stdout is exactly one JSON document, and exit codes distinguish
|
|
78
|
+
authentication (3) from network (4) from server (5) so a scheduled job can alert on the right thing.
|
|
79
|
+
|
|
80
|
+
## As a library
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
from geodeploy import Client
|
|
84
|
+
|
|
85
|
+
gd = Client("https://geodeploy.example.org", token="gdp_…")
|
|
86
|
+
result = gd.uploads.upload("roads.gpkg", wait=True)
|
|
87
|
+
portal = gd.portals.create("Roads")
|
|
88
|
+
gd.portals.add_layer(portal["id"], result.layer_id, "vector", {"color": "#e11d48"})
|
|
89
|
+
gd.portals.publish(portal["id"])
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Typed exceptions (`AuthError`, `PermissionError_`, `NotFoundError`, `ValidationError`,
|
|
93
|
+
`ServerError`, `TransportError`, `JobFailed`), progress callbacks and cancellation on uploads, and a
|
|
94
|
+
swappable transport so a desktop application can route requests through its own network stack.
|
|
95
|
+
|
|
96
|
+
## Documentation
|
|
97
|
+
|
|
98
|
+
Full guide: **<https://docs-geodeploy.kndev.org/cli/>**
|
|
99
|
+
|
|
100
|
+
Source and issues: **<https://github.com/bravemaster3/GeoDeploy>**
|
|
101
|
+
|
|
102
|
+
Licensed under the Apache License 2.0.
|
geodeploy-1.3.0/PYPI.md
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# geodeploy
|
|
2
|
+
|
|
3
|
+
**Command-line client and Python API for [GeoDeploy](https://github.com/bravemaster3/GeoDeploy)** —
|
|
4
|
+
the self-hosted spatial data platform and geoportal builder.
|
|
5
|
+
|
|
6
|
+
Upload data, style it, build a portal and publish it, without opening a browser:
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
pip install geodeploy
|
|
10
|
+
|
|
11
|
+
geodeploy login https://geodeploy.example.org --token gdp_…
|
|
12
|
+
geodeploy upload roads.gpkg sites.csv dem.tif --wait
|
|
13
|
+
geodeploy portals create "Field sites 2026" --publish
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
**No dependencies. Python 3.9+.** Every request goes through the standard library, so installing
|
|
17
|
+
this pulls in nothing else — and a QGIS plugin can vendor the same client without asking anyone to
|
|
18
|
+
pip-install into QGIS.
|
|
19
|
+
|
|
20
|
+
If your shell cannot find `geodeploy` after installing, pip's scripts directory is not on your
|
|
21
|
+
`PATH` — `python -m geodeploy` works regardless, and `pipx install geodeploy` or a virtual
|
|
22
|
+
environment avoids it.
|
|
23
|
+
|
|
24
|
+
## What it covers
|
|
25
|
+
|
|
26
|
+
- **Uploads** — shapefile, GeoPackage, GeoJSON, CSV (X/Y or WKT), GeoParquet, GeoTIFF. Many files
|
|
27
|
+
in one command; the route is chosen per file, and anything over 48 MB goes direct to object
|
|
28
|
+
storage in parallel presigned parts, so a multi-gigabyte upload survives a proxy that caps
|
|
29
|
+
request bodies.
|
|
30
|
+
- **Layers** — list, inspect, rename, share (STAC / OGC API - Features opt-in), share links per
|
|
31
|
+
tool, download, delete, restart a stalled ingest.
|
|
32
|
+
- **Symbology** — colour, opacity, outlines, dashes, markers, raster colormaps and stretches, plus
|
|
33
|
+
data-driven styling: `--color-field pop --classify jenks --classes 6 --ramp magma`, proportional
|
|
34
|
+
size, and attribute-driven 3D. Classification is computed by the instance, with the same code the
|
|
35
|
+
portal editor uses, so the CLI and the editor can never disagree about a class.
|
|
36
|
+
- **Portals** — create (web map, story map or catalog), arrange and style layers, folders, About
|
|
37
|
+
page, assets, access tiers, publish, and a whole-configuration JSON round trip for version
|
|
38
|
+
control.
|
|
39
|
+
- **Everything else** — external WMS/XYZ/WFS services, registering data already in PostGIS or the
|
|
40
|
+
bucket, the public STAC and OGC API - Features catalog, ingest jobs, users, and instance
|
|
41
|
+
administration (health, services, updates, backups, activity log).
|
|
42
|
+
|
|
43
|
+
Every command takes `--json`, where stdout is exactly one JSON document, and exit codes distinguish
|
|
44
|
+
authentication (3) from network (4) from server (5) so a scheduled job can alert on the right thing.
|
|
45
|
+
|
|
46
|
+
## As a library
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
from geodeploy import Client
|
|
50
|
+
|
|
51
|
+
gd = Client("https://geodeploy.example.org", token="gdp_…")
|
|
52
|
+
result = gd.uploads.upload("roads.gpkg", wait=True)
|
|
53
|
+
portal = gd.portals.create("Roads")
|
|
54
|
+
gd.portals.add_layer(portal["id"], result.layer_id, "vector", {"color": "#e11d48"})
|
|
55
|
+
gd.portals.publish(portal["id"])
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Typed exceptions (`AuthError`, `PermissionError_`, `NotFoundError`, `ValidationError`,
|
|
59
|
+
`ServerError`, `TransportError`, `JobFailed`), progress callbacks and cancellation on uploads, and a
|
|
60
|
+
swappable transport so a desktop application can route requests through its own network stack.
|
|
61
|
+
|
|
62
|
+
## Documentation
|
|
63
|
+
|
|
64
|
+
Full guide: **<https://docs-geodeploy.kndev.org/cli/>**
|
|
65
|
+
|
|
66
|
+
Source and issues: **<https://github.com/bravemaster3/GeoDeploy>**
|
|
67
|
+
|
|
68
|
+
Licensed under the Apache License 2.0.
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
# cli/
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
The packaged **`geodeploy`** command-line client **and** the Python API client it is built on — the
|
|
5
|
+
headless half of the product (roadmap v1.3). Two audiences, one package: a person or CI job at a
|
|
6
|
+
shell, and the **QGIS plugin** (`E-05`), which imports `geodeploy.client` directly rather than
|
|
7
|
+
re-implementing HTTP against the same endpoints.
|
|
8
|
+
|
|
9
|
+
User documentation is `docs/cli.md`; this file is the technical note.
|
|
10
|
+
|
|
11
|
+
## Contents
|
|
12
|
+
|
|
13
|
+
**The library** (`geodeploy/`) — never prints, never exits, raises typed errors:
|
|
14
|
+
- `client.py` — `Client`: URL/auth resolution, request plumbing, error mapping, and the namespaces
|
|
15
|
+
(`gd.vector`, `gd.raster`, `gd.layers`, `gd.portals`, `gd.sources`, `gd.imports`, `gd.jobs`,
|
|
16
|
+
`gd.uploads`, `gd.admin`, `gd.users`, `gd.catalog`). `absolute()` resolves the **relative
|
|
17
|
+
`/s3/…` presigned URLs** a managed MinIO returns.
|
|
18
|
+
- `transport.py` — stdlib HTTP. `UrllibTransport` (retries only connection failures and 502/503/504,
|
|
19
|
+
and **never** a streamed body — the file object is already partly consumed), `MultipartBody`
|
|
20
|
+
(form-data streamed from disk with a real Content-Length), `ProgressReader`. Anything with
|
|
21
|
+
`send(Request) -> Response` can replace it; that seam is for QGIS's `QgsNetworkAccessManager`.
|
|
22
|
+
- `config.py` — profiles + credentials. **Tokens never enter `config.json`**: OS keyring when there
|
|
23
|
+
is one, else `credentials.json` at 0600 in a 0700 directory, written atomically. `normalize_url`
|
|
24
|
+
collapses `…/api`, trailing slashes and a bare host to one origin, so a credential saved under one
|
|
25
|
+
spelling is found under another. `split_portal_url` does the same job for a published portal's
|
|
26
|
+
URL → `(origin, slug)`, so a link copied out of the address bar is a valid portal reference
|
|
27
|
+
anywhere a slug is (`browse --portal`, and `Portals.get`, which every portal command goes
|
|
28
|
+
through). `resolve()` = flag → env → profile, for URL and token separately.
|
|
29
|
+
- `uploads.py` — the routing table (below) plus both transports: multipart-through-the-API, and
|
|
30
|
+
presign/chunked direct-to-storage with per-part retry and abort-on-failure.
|
|
31
|
+
- `layers.py` — the two layer kinds, and `Layers.resolve` (id | uid | `vector-3` | name; ambiguity
|
|
32
|
+
raises rather than guessing — including a **bare integer that exists in both kinds**, since
|
|
33
|
+
vector and raster ids are separate sequences and "1" is routinely two different layers).
|
|
34
|
+
`download_dataset()` pulls a prepared GeoParquet layer's manifest + every partition straight from
|
|
35
|
+
storage: complete, lossless and **not subject to the export row cap**, which is why
|
|
36
|
+
`layers download` prefers it over queueing an export for those layers.
|
|
37
|
+
- `portals.py` — portal CRUD and `layer_configs` surgery. `layer_configs[0]` = top of the list =
|
|
38
|
+
drawn on top. `editable_config()` drops server-owned fields before a round-trip PUT.
|
|
39
|
+
- `styles.py` — the style vocabulary of `api/geodeploy/services/symbology.py`, in both directions.
|
|
40
|
+
`build_style()` **assembles** it from plain arguments; `parse()`/`Style` **reads** one back
|
|
41
|
+
(mode, field, classes, categories, size, extrusion, rescale) so a consumer — the QGIS plugin —
|
|
42
|
+
does not re-decide what `color_mode: "graduated"` implies. `Style` is a reader, not a schema: it
|
|
43
|
+
never rejects and keeps `.raw`, and `to_dict()` makes build → parse → build lossless.
|
|
44
|
+
**Classification maths is NOT reimplemented**: `classify()` reads
|
|
45
|
+
`GET /data/vector/{ref}/field-stats`, so the CLI cannot disagree with the editor about which class
|
|
46
|
+
a feature is in. Same rule for legends — `vector.legend()` asks the instance, and
|
|
47
|
+
`Style.legend()` is the local twin for a style that has no URL yet (an unsaved edit), pinned
|
|
48
|
+
against the server's exact labels in `test_styles_jobs`.
|
|
49
|
+
- `catalog.py` — the public surfaces, including `public()` (the instance index behind
|
|
50
|
+
`geodeploy browse`) and `portal_style()`, which reads a published portal's own `style.json`.
|
|
51
|
+
- `jobs.py`, `sources.py`, `imports.py`, `admin.py`, `errors.py`.
|
|
52
|
+
|
|
53
|
+
`Layers.resolve_public()` is the anonymous twin of `resolve()`: with no credential a layer is looked
|
|
54
|
+
up in the public index, so `geodeploy --url … layers download roads` works by NAME for someone with
|
|
55
|
+
no account. `_LayerBase.export_to_file()` drives the queue-poll-download of a built export.
|
|
56
|
+
|
|
57
|
+
**The CLI** (`geodeploy/cli/`) — the only part allowed to print:
|
|
58
|
+
- `main.py` — argparse root, `Context` (formatter + lazy client), and the single place exceptions
|
|
59
|
+
become exit codes.
|
|
60
|
+
- `output.py` — `Formatter` (stdout = the answer, stderr = commentary, `--json` = one document),
|
|
61
|
+
tables, progress bar, `EXIT_*` constants.
|
|
62
|
+
- `commands/` — one module per group (`auth`, `upload`, `layers`, `portals`, `sources`, `imports`,
|
|
63
|
+
`jobs`, `catalog`, `admin`, `browse`), each with `register(subparsers)`. `_common.py` holds the
|
|
64
|
+
styling flags, shared by the three commands that take them, and `resolve_layer(..., public_ok=)`
|
|
65
|
+
which decides between the authenticated list and the public index.
|
|
66
|
+
|
|
67
|
+
`tests/` — 317 tests against a real HTTP server (`conftest.FakeInstance`) that records what arrived
|
|
68
|
+
on the wire. `pyproject.toml` — packaging; console script `geodeploy`.
|
|
69
|
+
|
|
70
|
+
## Dependencies / relationships
|
|
71
|
+
- **Zero runtime dependencies, Python 3.9+.** Both constraints exist for the QGIS plugin, which
|
|
72
|
+
vendors this package and cannot pip-install anything on a user's machine. The floor is set by the
|
|
73
|
+
**oldest QGIS anyone still runs**, not the current one — institutions pin a release for years —
|
|
74
|
+
so it reaches back through several LTR lines rather than tracking today's. Current QGIS ships a
|
|
75
|
+
much newer Python; that is not the constraint. Do not add a dependency here without moving it
|
|
76
|
+
into an extra.
|
|
77
|
+
- Consumes `api/geodeploy/routers/` — see that folder's README for the permission model. Nothing in
|
|
78
|
+
the API imports this.
|
|
79
|
+
- **GeoLibre does not use this package**: its plugin is browser TypeScript
|
|
80
|
+
(`integrations/geolibre-plugin/`) hitting `/api/interop/geolibre/*`. What the two share is the
|
|
81
|
+
HTTP contract and the style vocabulary, not code.
|
|
82
|
+
- Parity to keep: the upload routing mirrors `ui/src/composables/useUpload.js`, and the style keys
|
|
83
|
+
mirror `services/symbology.py` + `ui/src/lib/symbology.js`.
|
|
84
|
+
|
|
85
|
+
## The upload routing table (and why 48 MB)
|
|
86
|
+
`.gpkg/.geojson/.json/.zip` < 48 MB → `POST /data/vector/upload`; `.csv` < 48 MB → `/upload-csv`
|
|
87
|
+
(PostGIS); either at/over 48 MB → presigned direct-to-storage → `/large/complete` (converted to
|
|
88
|
+
GeoParquet); `.parquet` at any size → `/geoparquet/{presign,complete}`; `.tif` small/large →
|
|
89
|
+
`/data/raster/upload` or chunked → `/data/raster/large/complete`. Over 48 MB the upload is chunked
|
|
90
|
+
into 48 MB presigned parts, four in parallel.
|
|
91
|
+
|
|
92
|
+
**48 MB is not the API's 2 GB cap.** The binding limit is the request-body cap of whatever proxy
|
|
93
|
+
sits in front of the instance (Cloudflare's free tier: 100 MB), and exceeding it produces *no server
|
|
94
|
+
log at all* — the request never arrives. `LARGE_UPLOAD_THRESHOLD` must stay equal to the UI's.
|
|
95
|
+
|
|
96
|
+
## Packaging and release
|
|
97
|
+
|
|
98
|
+
`pyproject.toml` follows PEP 639: `license = "Apache-2.0"` as an SPDX expression plus
|
|
99
|
+
`license-files`, and **no `License ::` classifier** — setuptools ≥ 77 refuses a build that declares
|
|
100
|
+
both. The version is single-sourced from `geodeploy/__init__.py` (`dynamic = ["version"]`), so
|
|
101
|
+
`--version`, the user agent and the package metadata cannot disagree.
|
|
102
|
+
|
|
103
|
+
**`PYPI.md`, not `README.md`, is the project page.** This file is the folder's technical note (the
|
|
104
|
+
repo convention in CLAUDE.md), which is not what someone landing on PyPI wants to read; `PYPI.md` is
|
|
105
|
+
the user-facing front page. Keep both current.
|
|
106
|
+
|
|
107
|
+
The name `geodeploy` was unregistered on PyPI as of 2026-08-13 (re-check immediately before the
|
|
108
|
+
first upload — anyone can claim it).
|
|
109
|
+
|
|
110
|
+
**Releases go through `.github/workflows/publish-cli.yml`, not from a laptop.** It uses PyPI's
|
|
111
|
+
Trusted Publishing: no API token exists to leak or rotate, and the workflow gates the upload on the
|
|
112
|
+
test suite, on `twine check`, on a clean `--no-deps` install, and on the **tag matching the packaged
|
|
113
|
+
version**. Cutting a release is therefore:
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
# 1. bump the one line in geodeploy/__init__.py, commit, merge
|
|
117
|
+
# 2. tag it — the prefix is `cli-v`, not the platform's `v1.3`
|
|
118
|
+
git tag cli-v1.3.0b1 && git push origin cli-v1.3.0b1
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Use the workflow's manual run (`Actions → Publish CLI → Run workflow → testpypi`) to rehearse
|
|
122
|
+
against TestPyPI first. Building by hand is still the way to CHECK a change without releasing it:
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
cd cli
|
|
126
|
+
python -m build # sdist + wheel into dist/
|
|
127
|
+
python -m twine check dist/* # must pass before anything is uploaded
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
`dist/` and `build/` are git-ignored. The sdist carries the tests, so the release can be verified
|
|
131
|
+
from source rather than trusted.
|
|
132
|
+
|
|
133
|
+
**Versioning.** The CLI's number tracks the GeoDeploy release it ships with, so it must not claim a
|
|
134
|
+
release that has not happened. `1.3.0b1` went up first for exactly that reason — a version on PyPI
|
|
135
|
+
can never be re-uploaded, and deleting it does not free the number, so a pre-release is the only way
|
|
136
|
+
to test the real index without spending the one that matters. `1.3.0` followed when v1.3 tagged.
|
|
137
|
+
Do the same next time: `1.4.0bN` while v1.4 is unreleased, then the final.
|
|
138
|
+
|
|
139
|
+
## The row cap, and the two ways around it
|
|
140
|
+
|
|
141
|
+
A **built** export (`gpkg`/`csv`/`geojson`, or anything clipped) stops at `FULL_EXPORT_CAP`
|
|
142
|
+
(1,000,000 features, env-tunable) for a whole layer and `FEATURE_CAP` (50,000) for a bbox clip,
|
|
143
|
+
because the worker assembles the archive in memory. The failure mode this creates is not the cap —
|
|
144
|
+
it is that a truncated export has the same names and formats as a complete one. So:
|
|
145
|
+
|
|
146
|
+
- the task records the row count of every file it writes, marks the ones that reached the cap in
|
|
147
|
+
`MANIFEST.txt` (with the uncapped alternatives spelled out), and writes `{job_id}.json` beside
|
|
148
|
+
the zip;
|
|
149
|
+
- `GET …/export-status/{job_id}` reports `truncated: [{file, rows, cap}]` from that file;
|
|
150
|
+
- `layers download` prints `INCOMPLETE`, names the alternative, and **exits non-zero**.
|
|
151
|
+
|
|
152
|
+
The uncapped paths, which the CLI prefers automatically where it can: a prepared **GeoParquet**
|
|
153
|
+
layer's own partition files (`download_dataset`, no worker involved), and **OGC API - Features**
|
|
154
|
+
paging for PostGIS (`ogr2ogr -f GPKG out.gpkg "OAPIF:<instance>/api/ogc" <layer>` — not wrapped by
|
|
155
|
+
the CLI yet; see below).
|
|
156
|
+
|
|
157
|
+
## Current status & known issues
|
|
158
|
+
- `geodeploy browse` and `layers download` depend on API endpoints added in the same branch
|
|
159
|
+
(`/api/public`, `/data/{kind}/{ref}/export`). An older instance answers 404 for both; the browse
|
|
160
|
+
command says so, but `layers download` for a built format will simply fail against one.
|
|
161
|
+
- Built 2026-08-12 on branch `feat/cli`; **not yet exercised against a live instance** — every test
|
|
162
|
+
runs against the in-repo fake. First real run should be `upload --dry-run`, then a small upload,
|
|
163
|
+
then a portal round trip.
|
|
164
|
+
- Not published to PyPI yet. `pip install -e cli/` is the current install.
|
|
165
|
+
- `geodeploy admin update --watch` polls through the API restart the update itself causes; a failed
|
|
166
|
+
poll is treated as progress, which is right but means a genuinely dead instance looks like a slow
|
|
167
|
+
update until the poll loop is interrupted.
|
|
168
|
+
- No declarative `apply` (a manifest describing layers + portal, applied idempotently). The
|
|
169
|
+
`portals export/import` round trip is the seam it would build on. Deliberately deferred.
|
|
170
|
+
- No shell completion.
|
|
171
|
+
- The QGIS plugin does not exist yet; the seams it needs (pluggable transport, progress callbacks,
|
|
172
|
+
`cancel`, typed errors, no printing, and `styles.Style` for reading a style back) are in place and
|
|
173
|
+
tested.
|
|
174
|
+
**Which URL it should hand QGIS is two questions, not one.** For DISPLAY of a heavy layer,
|
|
175
|
+
**PMTiles** is the fastest thing we serve — pre-tiled, range-requested, no per-pan query — and it
|
|
176
|
+
is what `layers links` should offer first for a big GeoParquet layer. But PMTiles is a *rendering*
|
|
177
|
+
format: generalised geometry, tile-clipped features, attributes trimmed to what the tiles carry.
|
|
178
|
+
For DATA — full attributes, exact geometry, analysis, editing — the answer is OGC API - Features,
|
|
179
|
+
the GeoParquet partitions, or a built export. A plugin that offers only one of the two will be
|
|
180
|
+
wrong half the time, so the layer-add dialog needs both, labelled for what they are.
|
|
181
|
+
Caveat: reading PMTiles needs a GDAL with the support (3.8+), which rules out the oldest QGIS the
|
|
182
|
+
Python floor above deliberately still supports — so the display path must degrade to OAPIF rather
|
|
183
|
+
than assume it.
|
|
184
|
+
**Measured on the maintainer's two installs (2026-08-13)**, which is why this is a rule and not a
|
|
185
|
+
worry: one reports Python **3.9.5** / GDAL **3.7.2** (no PMTiles — and the exact Python the floor
|
|
186
|
+
above exists for), the other Python **3.12.12** / GDAL **3.12.1** (PMTiles fine). Both are in
|
|
187
|
+
daily use. So `metadata.txt` should say `qgisMinimumVersion=3.28` (the oldest QGIS whose Python
|
|
188
|
+
is ≥ 3.9) and the plugin should branch on `gdal.VersionInfo()` at RUNTIME rather than refusing to
|
|
189
|
+
install — blocking the old one would lock out precisely the pinned installs this package's
|
|
190
|
+
constraints were chosen to serve.
|
|
191
|
+
Develop and demo on the NEWEST QGIS; that is a different decision from what the plugin refuses to
|
|
192
|
+
run on, and only the second one is visible to a user with an old install.
|
|
193
|
+
|
|
194
|
+
## Last updated
|
|
195
|
+
2026-08-12 (created — packaged CLI + Python client, replacing `examples/geodeploy_cli.py`; then
|
|
196
|
+
`browse` + anonymous layer download on top of the new `/api/public` and per-layer export endpoints)
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""GeoDeploy — command-line client and Python API.
|
|
2
|
+
|
|
3
|
+
from geodeploy import Client
|
|
4
|
+
gd = Client("https://geodeploy.example.org", token="gdp_…")
|
|
5
|
+
layer = gd.uploads.upload("roads.gpkg", wait=True)
|
|
6
|
+
portal = gd.portals.create("Roads")
|
|
7
|
+
gd.portals.add_layer(portal["id"], layer.layer_id, "vector", {"color": "#e11d48"})
|
|
8
|
+
gd.portals.publish(portal["id"])
|
|
9
|
+
|
|
10
|
+
Zero runtime dependencies, Python 3.9+, so the QGIS plugin can vendor this package as-is.
|
|
11
|
+
"""
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
# The CLI's version tracks the GeoDeploy release it ships with. A PyPI version can never be
|
|
15
|
+
# re-uploaded, so a number is only spent once the release it names exists: 1.3.0b1 proved the
|
|
16
|
+
# packaging against the real index, and this is the release it was rehearsing for.
|
|
17
|
+
__version__ = "1.3.0"
|
|
18
|
+
|
|
19
|
+
from .client import Client # noqa: E402 (after __version__ — the user agent reads it)
|
|
20
|
+
from .errors import ( # noqa: E402
|
|
21
|
+
APIError,
|
|
22
|
+
AuthError,
|
|
23
|
+
ConfigError,
|
|
24
|
+
ConflictError,
|
|
25
|
+
GeoDeployError,
|
|
26
|
+
NotFoundError,
|
|
27
|
+
PermissionError_,
|
|
28
|
+
ServerError,
|
|
29
|
+
TransportError,
|
|
30
|
+
ValidationError,
|
|
31
|
+
)
|
|
32
|
+
from .jobs import JobFailed, JobTimeout # noqa: E402
|
|
33
|
+
from .styles import Style, parse_style # noqa: E402
|
|
34
|
+
|
|
35
|
+
__all__ = [
|
|
36
|
+
"Client",
|
|
37
|
+
"Style",
|
|
38
|
+
"parse_style",
|
|
39
|
+
"GeoDeployError",
|
|
40
|
+
"APIError",
|
|
41
|
+
"AuthError",
|
|
42
|
+
"ConfigError",
|
|
43
|
+
"ConflictError",
|
|
44
|
+
"NotFoundError",
|
|
45
|
+
"PermissionError_",
|
|
46
|
+
"ServerError",
|
|
47
|
+
"TransportError",
|
|
48
|
+
"ValidationError",
|
|
49
|
+
"JobFailed",
|
|
50
|
+
"JobTimeout",
|
|
51
|
+
"__version__",
|
|
52
|
+
]
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""`python -m geodeploy` — the same entry point as the `geodeploy` console script.
|
|
2
|
+
|
|
3
|
+
Worth having: inside QGIS's bundled Python, or a virtualenv whose `Scripts/` is not on PATH, the
|
|
4
|
+
module form is the one that works.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import sys
|
|
9
|
+
|
|
10
|
+
from .cli.main import main
|
|
11
|
+
|
|
12
|
+
if __name__ == "__main__":
|
|
13
|
+
sys.exit(main())
|