Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/continuous_integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ jobs:
name: Run unit tests & build
strategy:
matrix:
python_version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
python_version: ["3.9", "3.10", "3.11", "3.12"]
os: [ubuntu-latest]

runs-on: ${{ matrix.os }}
Expand All @@ -48,8 +48,8 @@ jobs:
python-version: ${{ matrix.python_version }}
cache: 'pip'

- name: Install dependencies
run: pip install -r requirements.txt
- name: Install package and dependencies
run: pip install .

- name: Run unit tests
run: ENVIRONMENT=test python -m unittest discover
Expand Down
34 changes: 17 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,11 @@ Contributions are more than welcome!
Code style done with [precommit](https://pre-commit.com/).

```
pip install pre-commit
uv sync --group dev
# if you want to have git commits trigger pre-commit, install pre-commit hook:
pre-commit install
uv run pre-commit install
# else run manually before (re)staging your files:
pre-commit run --all-files
uv run pre-commit run --all-files
```

### Cloning the project
Expand All @@ -94,24 +94,21 @@ One time action to clone and setup:
```shell
git clone https://github.com/opengisch/qfieldcloud-sdk-python
cd qfieldcloud-sdk-python
# install dev dependencies
python3 -m pip install pipenv
pre-commit install
# install package in a virtual environment
pipenv install -r requirements.txt
# install dev dependencies (+ standard dependencies)
uv sync --group dev
uv run pre-commit install
```

To run CLI interface for development purposes execute:

```shell
pipenv shell # if your pipenv virtual environment is not active yet
python -m qfieldcloud_sdk
uv run python -m qfieldcloud_sdk
```

To ease development, you can set a `.env` file. Therefore you can use directly the `qfieldcloud-cli` executable:
```
cp .env.example .env
pipenv run qfieldcloud-cli
uv run qfieldcloud-cli
```

### Building the package
Expand All @@ -120,12 +117,15 @@ pipenv run qfieldcloud-cli
# make sure your shell is sourced to no virtual environment
deactivate
# build
python3 -m build
# now either activate your shell with
pipenv shell
uv run python -m build
# and install with
python -m pip install . --force-reinstall
# or manually ensure it's pipenv and not your global pip doing the installation
pipenv run pip install . --force-reinstall
uv run python -m pip install . --force-reinstall
```
Voilà!

### Running the tests

```shell
uv sync --group dev
ENVIRONMENT=test uv run python -m unittest discover -s tests
```
23 changes: 19 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ requires = [
build-backend = "setuptools.build_meta"

[project]
requires-python = ">=3.8"
requires-python = ">=3.9"
name = "qfieldcloud-sdk"
description = "The official QFieldCloud SDK and CLI."
authors = [
Expand All @@ -23,15 +23,25 @@ classifiers = [
"Intended Audience :: Information Technology",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: Implementation :: CPython",
"Topic :: Scientific/Engineering :: GIS",
]
dynamic = ["dependencies", "readme", "version"]
dependencies = [
"certifi>=2023.7.22",
"charset-normalizer>=3.2.0",
"click>=8.1.5",
"idna>=3.4",
"requests>=2.31.0",
"requests-toolbelt>=1.0.0",
"tqdm>=4.65.0",
"urllib3>=2.0.7",
"pathvalidate>=3.2.1",
]
dynamic = ["readme", "version"]

[project.optional-dependencies]
docs = [
Expand All @@ -41,6 +51,12 @@ docs = [
"fancyboxmd~=1.1"
]

[dependency-groups]
dev = [
"build>=1.2.2",
"pre-commit==4.2.0",
]

[project.scripts]
qfieldcloud-cli = "qfieldcloud_sdk.cli:cli"

Expand All @@ -55,7 +71,6 @@ packages = ["qfieldcloud_sdk"]

[tool.setuptools.dynamic]
readme = {file = ["README.md"], content-type = "text/markdown"}
dependencies = { file = ["requirements.txt"] }

[tool.setuptools-git-versioning]
enabled = true
Expand Down
13 changes: 10 additions & 3 deletions qfieldcloud_sdk/sdk.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@
import sys
import requests
import urllib3
import cgi

from enum import Enum
from pathlib import Path
from typing import Any, Callable, Dict, List, Optional, TypedDict, Union, cast
from urllib import parse as urlparse
from email.message import Message

from requests.adapters import HTTPAdapter, Retry
from requests_toolbelt.multipart.encoder import MultipartEncoderMonitor
Expand Down Expand Up @@ -456,8 +456,7 @@ def get_project_seed_xlsform(

return None

_value, params = cgi.parse_header(content_disposition)
filename = params.get("filename")
filename = self._get_filename_from_content_disposition(content_disposition)

if not filename:
logger.warning(
Expand All @@ -471,6 +470,14 @@ def get_project_seed_xlsform(

return str(path)

@staticmethod
def _get_filename_from_content_disposition(
content_disposition: str,
) -> Optional[str]:
message = Message()
message["Content-Disposition"] = content_disposition
return cast(Optional[str], message.get_filename())

def list_remote_files(
self, project_id: str, skip_metadata: bool = True
) -> List[Dict[str, Any]]:
Expand Down
9 changes: 0 additions & 9 deletions requirements.txt

This file was deleted.

11 changes: 11 additions & 0 deletions tests/test_cli_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,17 @@ def test_paginated_list_projects_include_public(self):
)
self.assertTrue(0 < len(results) and len(results) <= 50)

def test_parse_content_disposition_filename(self):
filename = Client._get_filename_from_content_disposition(
'attachment; filename="seed.xlsx"'
)
self.assertEqual(filename, "seed.xlsx")

encoded_filename = Client._get_filename_from_content_disposition(
"attachment; filename*=UTF-8''my%20seed.xlsx"
)
self.assertEqual(encoded_filename, "my seed.xlsx")


class TestCLI(unittest.TestCase):
@classmethod
Expand Down
Loading
Loading