diff --git a/.gitignore b/.gitignore
index 26b602e4..14535f82 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,3 +7,14 @@ bundles-util.js
qgis_resources.py
venv
.idea
+# Nixos Direnv Dev Environment
+.envrc
+shell.nix
+.vscode
+.vscode-extensions
+.venv
+.env
+.privoxy-config
+.privoxy-cache/ca-key.pem
+.privoxy-cache/ca-cert.pem
+.privoxy.pid
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 298b6585..bb243015 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -17,7 +17,7 @@ repos:
hooks:
- id: black
- repo: https://github.com/PyCQA/flake8
- rev: 3.9.2
+ rev: 6.0.0
hooks:
- id: flake8
language_version: python3
diff --git a/README.rst b/README.rst
index 0c08ecb0..46c3886e 100644
--- a/README.rst
+++ b/README.rst
@@ -127,7 +127,11 @@ To install the latest version of the plugin:
That will copy the code into your QGIS user plugin folder, or create a
symlink in it, depending on your OS.
- **NOTE**: This ``paver`` task only installs to the 'default' QGIS profile; so, you will have to ensure that is the active profile in order to see the plugin. You will also need to initially activate the plugin inside of the QGIS plugin manager.
+ **NOTE**: By default, this ``paver`` task installs the plugin to the 'default' QGIS profile. However, you can optionally specify a custom plugin path using the `--pluginpath` flag. For example:
+
+ paver install --pluginpath=~/.local/share/QGIS/QGIS3/profiles/custom_profile/python/plugins
+
+ If you specify a custom `pluginpath`, the plugin will be installed to the specified location. Ensure that the specified profile is active in QGIS to see the plugin. You will also need to activate the plugin inside the QGIS plugin manager.
- To package the plugin (*not needed during development*), run
@@ -136,3 +140,9 @@ To install the latest version of the plugin:
Documentation will be built in the `docs` folder and added to the resulting
zip file. It includes dependencies as well, but it will not download them, so
the `setup` task has to be run before packaging.
+
+- NixOS users will find a flake in the root of the repository, which can be used to create a development shell with all dependencies installed. To use it, run:
+
+ nix develop
+
+ This will set up a development environment with all necessary dependencies for the plugin.
diff --git a/flake.lock b/flake.lock
new file mode 100644
index 00000000..3b54e324
--- /dev/null
+++ b/flake.lock
@@ -0,0 +1,121 @@
+{
+ "nodes": {
+ "flake-parts": {
+ "inputs": {
+ "nixpkgs-lib": "nixpkgs-lib"
+ },
+ "locked": {
+ "lastModified": 1748821116,
+ "narHash": "sha256-F82+gS044J1APL0n4hH50GYdPRv/5JWm34oCJYmVKdE=",
+ "owner": "hercules-ci",
+ "repo": "flake-parts",
+ "rev": "49f0870db23e8c1ca0b5259734a02cd9e1e371a1",
+ "type": "github"
+ },
+ "original": {
+ "owner": "hercules-ci",
+ "repo": "flake-parts",
+ "type": "github"
+ }
+ },
+ "flake-utils": {
+ "locked": {
+ "lastModified": 1659877975,
+ "narHash": "sha256-zllb8aq3YO3h8B/U0/J1WBgAL8EX5yWf5pMj3G0NAmc=",
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "rev": "c0e246b9b83f637f4681389ecabcb2681b4f3af0",
+ "type": "github"
+ },
+ "original": {
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "type": "github"
+ }
+ },
+ "geospatial": {
+ "inputs": {
+ "flake-parts": "flake-parts",
+ "nixgl": "nixgl",
+ "nixpkgs": "nixpkgs"
+ },
+ "locked": {
+ "lastModified": 1749111814,
+ "narHash": "sha256-QqriB8GtPn7hxAPyp7jIk3a2555p75/kmB90n2abu5g=",
+ "owner": "imincik",
+ "repo": "geospatial-nix.repo",
+ "rev": "41f92ade4891a67c44d06ac37dcc225a3b123160",
+ "type": "github"
+ },
+ "original": {
+ "owner": "imincik",
+ "repo": "geospatial-nix.repo",
+ "type": "github"
+ }
+ },
+ "nixgl": {
+ "inputs": {
+ "flake-utils": "flake-utils",
+ "nixpkgs": [
+ "geospatial",
+ "nixpkgs"
+ ]
+ },
+ "locked": {
+ "lastModified": 1713543440,
+ "narHash": "sha256-lnzZQYG0+EXl/6NkGpyIz+FEOc/DSEG57AP1VsdeNrM=",
+ "owner": "nix-community",
+ "repo": "nixGL",
+ "rev": "310f8e49a149e4c9ea52f1adf70cdc768ec53f8a",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nix-community",
+ "repo": "nixGL",
+ "type": "github"
+ }
+ },
+ "nixpkgs": {
+ "locked": {
+ "lastModified": 1748856973,
+ "narHash": "sha256-RlTsJUvvr8ErjPBsiwrGbbHYW8XbB/oek0Gi78XdWKg=",
+ "owner": "NixOS",
+ "repo": "nixpkgs",
+ "rev": "e4b09e47ace7d87de083786b404bf232eb6c89d8",
+ "type": "github"
+ },
+ "original": {
+ "owner": "NixOS",
+ "ref": "nixpkgs-unstable",
+ "repo": "nixpkgs",
+ "type": "github"
+ }
+ },
+ "nixpkgs-lib": {
+ "locked": {
+ "lastModified": 1748740939,
+ "narHash": "sha256-rQaysilft1aVMwF14xIdGS3sj1yHlI6oKQNBRTF40cc=",
+ "owner": "nix-community",
+ "repo": "nixpkgs.lib",
+ "rev": "656a64127e9d791a334452c6b6606d17539476e2",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nix-community",
+ "repo": "nixpkgs.lib",
+ "type": "github"
+ }
+ },
+ "root": {
+ "inputs": {
+ "geospatial": "geospatial",
+ "nixpkgs": [
+ "geospatial",
+ "nixpkgs"
+ ]
+ }
+ }
+ },
+ "root": "root",
+ "version": 7
+}
diff --git a/flake.nix b/flake.nix
new file mode 100644
index 00000000..b79735e0
--- /dev/null
+++ b/flake.nix
@@ -0,0 +1,160 @@
+{
+ description = "NixOS developer environment for QGIS plugins.";
+
+ inputs.geospatial.url = "github:imincik/geospatial-nix.repo";
+ inputs.nixpkgs.follows = "geospatial/nixpkgs";
+
+ outputs = { self, geospatial, nixpkgs }:
+ let
+ system = "x86_64-linux";
+ profileName = "PLANET";
+ pkgs = import nixpkgs {
+ inherit system;
+ config = { allowUnfree = true; };
+ };
+ extraPythonPackages = ps: [
+ ps.pyqtwebengine
+ ps.jsonschema
+ ps.debugpy
+ ps.future
+ ps.psutil
+ ];
+ qgisWithExtras = geospatial.packages.${system}.qgis.override {
+ inherit extraPythonPackages;
+ };
+ qgisLtrWithExtras = geospatial.packages.${system}.qgis-ltr.override {
+ inherit extraPythonPackages;
+ };
+ in {
+ packages.${system} = {
+ default = qgisWithExtras;
+ qgis-ltr = qgisLtrWithExtras;
+ };
+
+ devShells.${system}.default = pkgs.mkShell {
+ packages = [
+ pkgs.chafa
+ pkgs.ffmpeg
+ pkgs.gdb
+ pkgs.git
+ pkgs.glow # terminal markdown viewer
+ pkgs.gource # Software version control visualization
+ pkgs.gum
+ pkgs.gum # UX for TUIs
+ pkgs.jq
+ pkgs.libsForQt5.kcachegrind
+ pkgs.nixfmt-rfc-style
+ pkgs.pre-commit
+ pkgs.pyprof2calltree # needed to covert cprofile call trees into a format kcachegrind can read
+ pkgs.python3
+ pkgs.qgis
+ pkgs.qt5.full # so we get designer
+ pkgs.qt5.qtbase
+ pkgs.qt5.qtlocation
+ pkgs.qt5.qtquickcontrols2
+ pkgs.qt5.qtsvg
+ pkgs.qt5.qttools
+ pkgs.skate # Distributed key/value store
+ pkgs.vim
+ pkgs.virtualenv
+ pkgs.vscode
+ pkgs.privoxy
+ (pkgs.python3.withPackages (ps: [
+ ps.python
+ ps.pip
+ ps.setuptools
+ ps.wheel
+ ps.pytest
+ ps.pytest-qt
+ ps.black
+ ps.click # needed by black
+ ps.jsonschema
+ ps.pandas
+ ps.odfpy
+ ps.psutil
+ ps.httpx
+ ps.toml
+ ps.typer
+ ps.paver
+ # For autocompletion in vscode
+ ps.pyqt5-stubs
+ ps.debugpy
+ ps.numpy
+ ps.gdal
+ ps.toml
+ ps.typer
+ ps.snakeviz # For visualising cprofiler outputs
+ ]))
+
+ ];
+ shellHook = ''
+ unset SOURCE_DATE_EPOCH
+
+ # Create a virtual environment in .venv if it doesn't exist
+ if [ ! -d ".venv" ]; then
+ python -m venv .venv
+ fi
+
+ # Activate the virtual environment
+ source .venv/bin/activate
+
+ # Upgrade pip and install packages from requirements.txt if it exists
+ pip install --upgrade pip > /dev/null
+ if [ -f requirements.txt ]; then
+ echo "Installing Python requirements from requirements.txt..."
+ pip install -r requirements.txt > .pip-install.log 2>&1
+ if [ $? -ne 0 ]; then
+ echo "β Pip install failed. See .pip-install.log for details."
+ fi
+ else
+ echo "No requirements.txt found, skipping pip install."
+ fi
+
+ echo "-----------------------"
+ echo "π Your Dev Environment is prepared."
+ echo "To run QGIS with your profile, use one of these commands:"
+ echo ""
+ echo " nix run .#qgis"
+ echo " nix run .#qgis-ltr"
+ echo ""
+ echo " Or use the helper script to launch it: "
+ echo " scripts/start_qgis.sh"
+ echo " scripts/start_qgis_ltr.sh"
+ echo ""
+ echo "π Note:"
+ echo "-----------------------"
+ echo "We provide a ready-to-use"
+ echo "VSCode environment which you"
+ echo "can start like this:"
+ echo ""
+ echo "scripts/vscode.sh"
+ echo "-----------------------"
+ echo "If you want to test the plugin behind an http proxy"
+ echo "we provide a script to run privoxy."
+ echo "π‘οΈ To start the proxy (Privoxy), run:"
+ echo " ./scripts/privoxy.sh start"
+ echo "π To stop the proxy, run:"
+ echo " ./scripts/privoxy.sh stop"
+ echo "-----------------------"
+ echo ""
+
+ pre-commit clean > /dev/null
+ pre-commit install --install-hooks > /dev/null
+ pre-commit run --all-files || true
+ '';
+ };
+
+ apps.${system} = {
+ qgis = {
+ type = "app";
+ program = "${qgisWithExtras}/bin/qgis";
+ args = [ "--profile" "${profileName}" ];
+ };
+ qgis-ltr = {
+ type = "app";
+ program = "${qgisLtrWithExtras}/bin/qgis";
+ args = [ "--profile" "${profileName}" ];
+ };
+ };
+ };
+}
diff --git a/pavement.py b/pavement.py
index a40043b6..4994fa45 100644
--- a/pavement.py
+++ b/pavement.py
@@ -26,7 +26,7 @@
import subprocess
import sys
import zipfile
-from configparser import SafeConfigParser
+from configparser import ConfigParser
from io import StringIO
from pathlib import Path
@@ -83,9 +83,7 @@ def setup():
try:
subprocess.check_call(
[
- sys.executable,
- "-m",
- "pip",
+ "pip", # Explicitly use pip instead of relying on sys.executable
"install",
"--no-deps",
"--upgrade",
@@ -100,25 +98,33 @@ def setup():
@task
+@cmdopts(
+ [
+ ("pluginpath=", "p", "Custom path to install the plugin"),
+ ]
+)
def install(options):
- """install plugin to qgis"""
+ """Install plugin to QGIS."""
plugin_name = options.plugin.name
src = path(__file__).dirname() / plugin_name
- if os.name == "nt":
- default_profile_plugins = (
+
+ # Use the plugin path provided via the command-line flag, or fallback to default paths
+ if hasattr(options, "pluginpath") and options.pluginpath:
+ dst_plugins = path(options.pluginpath).expanduser()
+ elif os.name == "nt":
+ dst_plugins = path(
"~/AppData/Roaming/QGIS/QGIS3/profiles/default/python/plugins"
- )
+ ).expanduser()
elif sys.platform == "darwin":
- default_profile_plugins = (
+ dst_plugins = path(
"~/Library/Application Support/QGIS/QGIS3"
"/profiles/default/python/plugins"
- )
+ ).expanduser()
else:
- default_profile_plugins = (
+ dst_plugins = path(
"~/.local/share/QGIS/QGIS3/profiles/default/python/plugins"
- )
+ ).expanduser()
- dst_plugins = path(default_profile_plugins).expanduser()
if not dst_plugins.exists():
os.makedirs(dst_plugins, exist_ok=True)
dst = dst_plugins / plugin_name
diff --git a/planet_explorer/gui/pe_basemap_layer_widget.py b/planet_explorer/gui/pe_basemap_layer_widget.py
index 447d43dd..d905c6e1 100644
--- a/planet_explorer/gui/pe_basemap_layer_widget.py
+++ b/planet_explorer/gui/pe_basemap_layer_widget.py
@@ -58,8 +58,7 @@
from ..planet_api import PlanetClient
TILE_URL_TEMPLATE = (
- "https://tiles.planet.com/basemaps/v1/planet-tiles/"
- "%s/gmap/{z}/{x}/{y}.png"
+ "https://tiles.planet.com/basemaps/v1/planet-tiles/" "%s/gmap/{z}/{x}/{y}.png"
)
@@ -304,7 +303,7 @@ def __init__(self, layer):
idx = 0
self.labelId = QLabel()
self.labelId.setText(
- f'{self.mosaicids[idx]}'
+ f'{self.mosaicids[idx]}' # noqa
)
self.layout.addWidget(self.labelId)
self.labelName = QLabel(current_mosaic_name)
@@ -352,7 +351,7 @@ def is_planet_basemap(self):
def on_value_changed(self, value):
self.labelId.setText(
- f'{self.mosaicids[value]}'
+ f'{self.mosaicids[value]}' # noqa
)
self.labelName.setText(f"{self.mosaicnames[value]}")
if not self.slider.isSliderDown():
@@ -368,8 +367,8 @@ def change_source(self):
res = pattern.search(unquote(self.layer.source()))
passed_api_key = res.groups()[0] if res.groups() else None
- if '&' in passed_api_key:
- passed_api_key = passed_api_key.split('&')[0]
+ if "&" in passed_api_key:
+ passed_api_key = passed_api_key.split("&")[0]
has_api_key = PlanetClient.getInstance().has_api_key()
@@ -381,7 +380,7 @@ def change_source(self):
api_key = (
PlanetClient.getInstance().api_key()
- if not passed_api_key or passed_api_key is ''
+ if not passed_api_key or passed_api_key == ""
else passed_api_key
)
@@ -391,9 +390,7 @@ def change_source(self):
self.slider.setVisible(has_api_key)
value = self.slider.value() if len(self.mosaics) > 1 else 0
name, mosaicid = self.mosaics[value]
- tile_url = TILE_URL_TEMPLATE % (
- mosaicid,
- )
+ tile_url = TILE_URL_TEMPLATE % (mosaicid,)
tile_url = f"{tile_url}?{quote(f'&api_key={str(api_key)}')}"
diff --git a/planet_explorer/gui/pe_basemaps_list_widget.py b/planet_explorer/gui/pe_basemaps_list_widget.py
index 41d54127..226c1f55 100644
--- a/planet_explorer/gui/pe_basemaps_list_widget.py
+++ b/planet_explorer/gui/pe_basemaps_list_widget.py
@@ -154,8 +154,8 @@ def __init__(self, mosaic):
self.mosaic = mosaic
title = mosaic_title(mosaic)
self.nameLabel = QLabel(
- f'{title}'
- f'
{mosaic[NAME]}'
+ f'{title}' # noqa
+ f'
{mosaic[NAME]}' # noqa
)
self.iconLabel = QLabel()
self.toolsButton = QLabel()
diff --git a/planet_explorer/gui/pe_basemaps_widget.py b/planet_explorer/gui/pe_basemaps_widget.py
index a8104e24..8293ecd3 100644
--- a/planet_explorer/gui/pe_basemaps_widget.py
+++ b/planet_explorer/gui/pe_basemaps_widget.py
@@ -589,8 +589,8 @@ def show_order_streaming_page(self):
name = selected[0][NAME]
dates = date_interval_from_mosaics(selected)
description = (
- f'{name}
'
- f'{len(selected)} instances | {dates}'
+ f'{name}
' # noqa
+ f'{len(selected)} instances | {dates}' # noqa
)
self.labelStreamingOrderDescription.setText(description)
pixmap = QPixmap(PLACEHOLDER_THUMB, "SVG")
@@ -639,8 +639,8 @@ def show_order_name_page(self):
dates = date_interval_from_mosaics(selected)
if self.radioDownloadComplete.isChecked():
description = (
- f'{name}
'
- f'{len(selected)} instances | {dates}'
+ f'{name}
' # noqa
+ f'{len(selected)} instances | {dates}' # noqa
)
title = "Order Complete Basemap"
@@ -653,8 +653,8 @@ def show_order_name_page(self):
numquads = len(selected_quads)
title = "Order Partial Basemap"
description = (
- f'{name}
'
- f'{self._quads_summary()}'
+ f'{name}
' # noqa
+ f'{self._quads_summary()}' # noqa
)
total_area = self._quads_quota()
@@ -673,7 +673,7 @@ def show_order_name_page(self):
size = numquads * QUAD_SIZE
if quota is not None:
self.labelOrderInfo.setText(
- f"This Order will use {total_area:.2f} square km"
+ f"This Order will use {total_area:.2f} square km" # noqa
f" of your remaining {quota} quota.\n\n"
f"This Order's download size will be approximately {size} GB."
)
diff --git a/planet_explorer/gui/pe_dailyimages_search_results_widget.py b/planet_explorer/gui/pe_dailyimages_search_results_widget.py
index 90fc026f..5d11f14a 100644
--- a/planet_explorer/gui/pe_dailyimages_search_results_widget.py
+++ b/planet_explorer/gui/pe_dailyimages_search_results_widget.py
@@ -619,7 +619,7 @@ def update_for_children(self):
self.children_count = size
text = f"""{self.date}
{PlanetClient.getInstance().item_types_names()[self.properties[ITEM_TYPE]]}
- {size} images"""
+ {size} images""" # noqa
self.nameLabel.setText(text)
geoms = []
@@ -689,7 +689,7 @@ def update_for_children(self):
)
self.children_count = size
text = f""" Satellite {self.satellite} {self.instrument}
- ({size} images)"""
+ ({size} images)""" # noqa
self.nameLabel.setText(text)
geoms = []
@@ -782,17 +782,15 @@ def _get_text(self):
if value == PlanetNodeMetadata.AREA_COVER:
area_coverage = area_coverage_for_image(self.image, self.request)
if area_coverage is not None:
- metadata += f"{value.value}:{area_coverage:.0f}{spacer}"
+ metadata += f"{value.value}:{area_coverage:.0f}{spacer}" # noqa
else:
- metadata += f"{value.value}:--{spacer}"
+ metadata += f"{value.value}:--{spacer}" # noqa
else:
- metadata += (
- f'{value.value}:{self.properties.get(value.value, "--")}{spacer}'
- )
+ metadata += f'{value.value}:{self.properties.get(value.value, "--")}{spacer}' # noqa
text = f"""{self.date} {self.time} UTC
{PlanetClient.getInstance().item_types_names()[self.properties[ITEM_TYPE]]}
{metadata}
- """
+ """ # noqa
return text
diff --git a/planet_explorer/gui/pe_explorer_dockwidget.py b/planet_explorer/gui/pe_explorer_dockwidget.py
index 60572d14..a9eb9b5b 100644
--- a/planet_explorer/gui/pe_explorer_dockwidget.py
+++ b/planet_explorer/gui/pe_explorer_dockwidget.py
@@ -211,7 +211,7 @@ def login(self, api_key=None):
f"api_key = {self.p_client.api_key()}\n\n"
f"user: {self.p_client.user()}\n\n"
)
- log.debug(f"Login successful:\n{specs}")
+ log.debug(f"Login successful:\n{specs}") # noqa
# Now switch panels
self.p_client.loginChanged.emit(self.p_client.has_api_key())
diff --git a/planet_explorer/gui/pe_orders.py b/planet_explorer/gui/pe_orders.py
index c3f17a0a..fb8a8f25 100644
--- a/planet_explorer/gui/pe_orders.py
+++ b/planet_explorer/gui/pe_orders.py
@@ -1056,7 +1056,7 @@ def _process_response(self, item_type: str, response: dict):
self._log(
f"Requesting {item_type} order failed: "
"response data contains no Order ID.\n"
- f"Order resp_data:\n{response}"
+ f"Order resp_data:\n{response}" # noqa
)
return False
diff --git a/planet_explorer/gui/pe_orders_monitor_dockwidget.py b/planet_explorer/gui/pe_orders_monitor_dockwidget.py
index ad9f4373..8e8ca95f 100755
--- a/planet_explorer/gui/pe_orders_monitor_dockwidget.py
+++ b/planet_explorer/gui/pe_orders_monitor_dockwidget.py
@@ -230,7 +230,7 @@ def __init__(self, order, dialog):
f"Order {order.name()}
"
f"Placed on: {order.date()}
"
"Id: '
+ f' href="https://www.planet.com/account/#/orders/{order.id()}">' # noqa
f"{order.id()}
"
f"Imagery source: {order.item_type()}
"
# f'Assets ordered: {order.assets_ordered()}
'
diff --git a/planet_explorer/gui/pe_planet_inspector_dockwidget.py b/planet_explorer/gui/pe_planet_inspector_dockwidget.py
index e8628aaa..bc8a58e0 100644
--- a/planet_explorer/gui/pe_planet_inspector_dockwidget.py
+++ b/planet_explorer/gui/pe_planet_inspector_dockwidget.py
@@ -283,7 +283,7 @@ def __init__(self, scene):
text = f"""{date} {time} UTC
{PlanetClient.getInstance().item_types_names()[self.properties['item_type']]}
- """
+ """ # noqa
self.nameLabel = QLabel(text)
self.iconLabel = QLabel()
diff --git a/planet_explorer/gui/pe_quads_treewidget.py b/planet_explorer/gui/pe_quads_treewidget.py
index 52743f04..f54aaa28 100644
--- a/planet_explorer/gui/pe_quads_treewidget.py
+++ b/planet_explorer/gui/pe_quads_treewidget.py
@@ -236,7 +236,7 @@ def __init__(self, quad):
self.setMouseTracking(True)
self.quad = quad
self.nameLabel = QLabel(
- f'{quad[ID]}
'
+ f'{quad[ID]}
' # noqa
f"{quad[PERCENT_COVERED]} % covered"
)
self.iconLabel = QLabel()
diff --git a/planet_explorer/gui/pe_tasking_dockwidget.py b/planet_explorer/gui/pe_tasking_dockwidget.py
index 888e00e6..a1f5d3da 100644
--- a/planet_explorer/gui/pe_tasking_dockwidget.py
+++ b/planet_explorer/gui/pe_tasking_dockwidget.py
@@ -126,7 +126,7 @@ def __init__(self, pt):
and contact our sales team
here.
-
Take me to the Tasking Dashboard
""" +Take me to the Tasking Dashboard
""" # noqa textbrowser.setHtml(text) layout.addWidget(textbrowser) self.setLayout(layout) @@ -135,9 +135,7 @@ def __init__(self, pt): def _link_clicked(self, url): if url.toString() == "dashboard": analytics_track(SKYSAT_TASK_CREATED) - url = ( - f"https://www.planet.com/tasking/orders/new/?geometry={self.pt.asWkt()}" - ) + url = f"https://www.planet.com/tasking/orders/new/?geometry={self.pt.asWkt()}" # noqa open_link_with_browser(url) self.close() else: @@ -194,11 +192,12 @@ def aoi_captured(self, rect, pt): transformed = transform.transform(pt) self.marker.setToGeometry(QgsGeometry.fromPointXY(transformed)) self._set_map_tool(False) - text = f""" -Selected Point Coordinates
-Latitude : {pt.y():.4f}
-Longitude : {pt.x():.4f}
- """ + + text = ( + f"Selected Point Coordinates
" + f'Latitude : {pt.y():.4f}
' # noqa + f'Longitude : {pt.x():.4f}
' # noqa + ) self.textBrowserPoint.setHtml(text) self.btnCancel.setEnabled(True) self.btnOpenDashboard.setEnabled(True) diff --git a/planet_explorer/pe_utils.py b/planet_explorer/pe_utils.py index ee969897..a0498708 100644 --- a/planet_explorer/pe_utils.py +++ b/planet_explorer/pe_utils.py @@ -226,8 +226,8 @@ def add_menu_section_action(text, menu, tag="b", pad=0.5): """ lbl = QLabel(f"<{tag}>{text}{tag}>", menu) lbl.setStyleSheet( - f"QLabel {{ padding-left: {pad}em; padding-right: {pad}em; " - f"padding-top: {pad}ex; padding-bottom: {pad}ex;}}" + f"QLabel {{ padding-left: {pad}em; padding-right: {pad}em; " # noqa: E702 E201 + f"padding-top: {pad}ex; padding-bottom: {pad}ex; }}" # noqa: E702 E202 ) wa = QWidgetAction(menu) wa.setDefaultWidget(lbl) @@ -356,7 +356,7 @@ def create_preview_group( uri = tile_service_data_src_uri(item_ids, service=tile_service) if uri: - log.debug(f"Tile datasource URI:\n{uri}") + log.debug(f"Tile datasource URI: \n{uri}") rlayer = QgsRasterLayer(uri, "Image previews", "wms") rlayer.setCustomProperty(PLANET_PREVIEW_ITEM_IDS, json.dumps(item_ids)) @@ -625,4 +625,6 @@ def plugin_version(add_commit=False): def user_agent(): - return f"qgis-{Qgis.QGIS_VERSION};planet-explorer{plugin_version()}" + return ( + f"qgis-{Qgis.QGIS_VERSION};planet-explorer{plugin_version()}" # noqa: E702 E231 + ) diff --git a/planet_explorer/planet_api/p_client.py b/planet_explorer/planet_api/p_client.py index 549a0099..a76cba2b 100644 --- a/planet_explorer/planet_api/p_client.py +++ b/planet_explorer/planet_api/p_client.py @@ -120,7 +120,7 @@ def set_proxy_values(self): proxyHost = settings.value("proxy/proxyHost") proxyPort = settings.value("proxy/proxyPort") - url = f"{proxyHost}:{proxyPort}" + url = f"{proxyHost}:{proxyPort}" # noqa authid = settings.value("proxy/authcfg", "") if authid: authConfig = QgsAuthMethodConfig() @@ -135,7 +135,7 @@ def set_proxy_values(self): if username: tokens = url.split("://") - url = f"{tokens[0]}://{username}:{password}@{tokens[-1]}" + url = f"{tokens[0]}://{username}:{password}@{tokens[-1]}" # noqa: E231 self.dispatcher.session.proxies["http"] = url self.dispatcher.session.proxies["https"] = url @@ -228,7 +228,8 @@ def get_quads_for_mosaic(self, mosaic, bbox=None, minimal=False): mosaicid = mosaic["id"] url = self._url( - f"basemaps/v1/mosaics/{mosaicid}/quads?bbox={bbox[0]},{bbox[1]},{bbox[2]},{bbox[3]}" + f"basemaps/v1/mosaics/{mosaicid}/quads?bbox=" + f"{bbox[0]}, {bbox[1]}, {bbox[2]}, {bbox[3]}" ) if bbox is None: if isinstance(mosaic, str): @@ -357,7 +358,7 @@ def update_user_quota(self): ).get_body() resp_data = resp.get() - log.debug(f"resp_data:\n{resp_data}") + log.debug(f"resp_data:\n{resp_data}") # noqa: E231 if not resp_data: log.warning("No response data found for getting quota") return False @@ -503,8 +504,8 @@ def tile_service_hash(item_type_ids: List[str]) -> Optional[str]: return res_json["name"] else: log.debug( - f"Tile service hash request failed:\n" - f"status_code: {res.status_code}\n" + f"Tile service hash request failed:\n" # noqa: E231 + f"status_code: {res.status_code}\n" # noqa: E231 f"reason: {res.reason}" ) diff --git a/planet_explorer/tests/install_plugin.py b/planet_explorer/tests/install_plugin.py index 71191b8a..32c1bad5 100755 --- a/planet_explorer/tests/install_plugin.py +++ b/planet_explorer/tests/install_plugin.py @@ -89,7 +89,7 @@ def error_catcher(msg, tag, level): assert utils.startPlugin(PLUGIN_KEY), f"'{PLUGIN_KEY}' failed to start!" assert ( PLUGIN_KEY in utils.active_plugins - ), f"'{PLUGIN_KEY}' not found in active_plugins, found: {utils.active_plugins}" + ), f"'{PLUGIN_KEY}' not found in active_plugins, found: {utils.active_plugins}" # noqa # Unload the plugin assert utils.unloadPlugin(PLUGIN_KEY), "'planet_explorer' failed to unload" diff --git a/planet_explorer/tests/test_basemaps.py b/planet_explorer/tests/test_basemaps.py index e3f69642..a7c80d0c 100644 --- a/planet_explorer/tests/test_basemaps.py +++ b/planet_explorer/tests/test_basemaps.py @@ -247,7 +247,7 @@ def test_basemaps_order_partial( break assert any( order_name in o_name for o_name in order_names - ), f"New order not present in orders list: {order_names}" + ), f"New order not present in orders list: {order_names}" # noqa # Download the basemap item_widget.download(is_unit_test=True) # noqa diff --git a/planet_explorer/tests/test_orders.py b/planet_explorer/tests/test_orders.py index e698073b..9d6f3b9a 100755 --- a/planet_explorer/tests/test_orders.py +++ b/planet_explorer/tests/test_orders.py @@ -242,7 +242,7 @@ def _order_dialog_interact(): assert any( order_name in o_name for o_name in order_names - ), f"New order not present in orders list: {order_names}" + ), f"New order not present in orders list: {order_names}" # noqa # Check for all order metadata except QuadOrder orders order_metadata = [ diff --git a/scripts/privoxy.sh b/scripts/privoxy.sh new file mode 100755 index 00000000..c5452b3d --- /dev/null +++ b/scripts/privoxy.sh @@ -0,0 +1,93 @@ +#!/usr/bin/env bash + +set -e + +CMD="${1:-start}" +PRIVOXY_CACHE_DIR="$(pwd)/.privoxy-cache" +PRIVOXY_CONFIG_FILE="$(pwd)/.privoxy-config" +PRIVOXY_PID_FILE="$(pwd)/.privoxy.pid" +PRIVOXY_CA_CERT_FILE="$PRIVOXY_CACHE_DIR/ca-cert.pem" +PRIVOXY_CA_KEY_FILE="$PRIVOXY_CACHE_DIR/ca-key.pem" + +mkdir -p "$PRIVOXY_CACHE_DIR" + +generate_ca() { + if [ ! -f "$PRIVOXY_CA_CERT_FILE" ] || [ ! -f "$PRIVOXY_CA_KEY_FILE" ]; then + echo "Generating CA certificate for HTTPS inspection..." + openssl req -x509 -newkey rsa:2048 -days 365 -nodes \ + -keyout "$PRIVOXY_CA_KEY_FILE" \ + -out "$PRIVOXY_CA_CERT_FILE" \ + -subj "/CN=Privoxy CA" + echo "CA certificate generated at: $PRIVOXY_CA_CERT_FILE" + else + echo "CA certificate already exists at: $PRIVOXY_CA_CERT_FILE" + fi +} + +show_help() { + echo "Usage: $0 {start|stop|restart|status|generate-ca|help}" + echo "" + echo "Commands:" + echo " start Start privoxy with HTTPS support" + echo " stop Stop privoxy" + echo " restart Restart privoxy" + echo " status Show privoxy status" + echo " generate-ca Generate a new CA certificate for HTTPS interception" + echo " help Show this help message" + echo "" + echo "The CA certificate is required for HTTPS interception. After running 'generate-ca'," + echo "import $PRIVOXY_CA_CERT_FILE into your browser/system to avoid HTTPS warnings." +} + +case "$CMD" in + start) + generate_ca + if [ ! -f "$PRIVOXY_CONFIG_FILE" ]; then + cat >"$PRIVOXY_CONFIG_FILE" <