From a7f9179c8f1a51c5b3804e9bcb2ec00b5dfc40d1 Mon Sep 17 00:00:00 2001 From: timbr_admin Date: Wed, 10 Dec 2025 11:47:27 +0200 Subject: [PATCH 1/5] Add UTF-8 encoding support for query data and update version to 2.1.0 --- pyproject.toml | 2 +- pytimbr_api/timbr_http_connector.py | 2 +- requirements.txt | Bin 34 -> 80 bytes test/test_encoding.py | 63 ++++++++++++++++++++++++++++ 4 files changed, 65 insertions(+), 2 deletions(-) create mode 100644 test/test_encoding.py diff --git a/pyproject.toml b/pyproject.toml index c7f905a..1f0e617 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "pytimbr_api" -version = "2.0.0" +version = "2.1.0" description = "Timbr REST API connector" readme = "README.md" license = "MIT" diff --git a/pytimbr_api/timbr_http_connector.py b/pytimbr_api/timbr_http_connector.py index 60e3fda..638d12c 100644 --- a/pytimbr_api/timbr_http_connector.py +++ b/pytimbr_api/timbr_http_connector.py @@ -66,7 +66,7 @@ def run_query( response = requests.post( f'{base_url}timbr/openapi/ontology/{ontology}/query{datasource_addition}', headers = headers, - data = query, + data = query.encode('utf-8') if isinstance(query, str) else query, verify = verify_ssl, ) return _parse_response(response) diff --git a/requirements.txt b/requirements.txt index b012b40001118167f08672073679e3345b1e69a8..008df9a770f0afee2fb71cc3c9952633c8678d45 100644 GIT binary patch delta 51 ycmY!in4qM`%fQ7@z);Ch!jQ^P3?%IsY#GcM^cYNl*oXlno5GL}md#@*V*mi%@dz#e delta 4 LcmWGgnxF�_*`L diff --git a/test/test_encoding.py b/test/test_encoding.py new file mode 100644 index 0000000..ee6e8c7 --- /dev/null +++ b/test/test_encoding.py @@ -0,0 +1,63 @@ +import pytest +from pytimbr_api.timbr_http_connector import run_query + + +def test_run_query_with_unicode_characters(test_config): + """Test that queries with unicode characters are properly encoded to UTF-8.""" + url = test_config['url'] + ontology = test_config['ontology'] + token = test_config['token'] + + # Query with unicode characters - using simple SELECT with unicode strings + # This tests that UTF-8 encoding works without requiring specific tables + query_with_unicode = "SELECT '测试' as chinese, 'café' as french, '😀' as emoji" + + results = run_query(url, ontology, token, query_with_unicode) + + assert results is not None, "Results should not be None for unicode query" + assert len(results) > 0, "Results should contain data" + + +def test_run_query_with_already_encoded_bytes(test_config): + """Test that queries already encoded as bytes are handled correctly.""" + url = test_config['url'] + ontology = test_config['ontology'] + token = test_config['token'] + + # Query already encoded as bytes + query_as_bytes = b"SELECT 1" + + results = run_query(url, ontology, token, query_as_bytes) + + assert results is not None, "Results should not be None for bytes query" + assert len(results) > 0, "Results should contain data" + + +def test_run_query_with_special_sql_characters(test_config): + """Test that queries with special SQL characters are properly encoded.""" + url = test_config['url'] + ontology = test_config['ontology'] + token = test_config['token'] + + # Query with special characters that need proper encoding + query_with_special_chars = "SELECT 'O''Brien' as name, '©' as copyright" + + results = run_query(url, ontology, token, query_with_special_chars) + + assert results is not None, "Results should not be None for query with special characters" + assert len(results) > 0, "Results should contain data" + + +def test_run_query_with_multilingual_text(test_config): + """Test that queries with multiple languages are properly encoded.""" + url = test_config['url'] + ontology = test_config['ontology'] + token = test_config['token'] + + # Query with multiple languages (Latin, Cyrillic, Arabic, Japanese) + query_multilingual = "SELECT 'Hello' as english, 'Привет' as russian, 'مرحبا' as arabic, 'こんにちは' as japanese" + + results = run_query(url, ontology, token, query_multilingual) + + assert results is not None, "Results should not be None for multilingual query" + assert len(results) > 0, "Results should contain data" From 18525ae5f1abc34f525b795ec8555ea8450eac0c Mon Sep 17 00:00:00 2001 From: timbr_admin Date: Wed, 10 Dec 2025 12:50:03 +0200 Subject: [PATCH 2/5] Update Python version requirements to 3.10 in workflow, README, and pyproject.toml --- .github/workflows/install-dependencies-and-run-tests.yml | 2 +- README.md | 3 +-- pyproject.toml | 3 +-- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/.github/workflows/install-dependencies-and-run-tests.yml b/.github/workflows/install-dependencies-and-run-tests.yml index 27651e2..2992742 100644 --- a/.github/workflows/install-dependencies-and-run-tests.yml +++ b/.github/workflows/install-dependencies-and-run-tests.yml @@ -10,7 +10,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.9", "3.10", "3.11", "3.12"] + python-version: ["3.10", "3.11", "3.12"] steps: - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} diff --git a/README.md b/README.md index 5183bcb..e3e4355 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,6 @@ [![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2FWPSemantix%2Ftimbr_python_http.svg?type=shield&issueType=license)](https://app.fossa.com/projects/git%2Bgithub.com%2FWPSemantix%2Ftimbr_python_http?ref=badge_shield&issueType=license) [![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2FWPSemantix%2Ftimbr_python_http.svg?type=shield&issueType=security)](https://app.fossa.com/projects/git%2Bgithub.com%2FWPSemantix%2Ftimbr_python_http?ref=badge_shield&issueType=security) -[![Python 3.9](https://img.shields.io/badge/python-3.9-blue)](https://www.python.org/downloads/release/python-3921/) [![Python 3.10](https://img.shields.io/badge/python-3.10-blue.svg)](https://www.python.org/downloads/release/python-31017/) [![Python 3.11](https://img.shields.io/badge/python-3.11-blue.svg)](https://www.python.org/downloads/release/python-31112/) [![Python 3.12](https://img.shields.io/badge/python-3.12-blue.svg)](https://www.python.org/downloads/release/python-3129/) @@ -15,7 +14,7 @@ This project is a pure python connector to timbr (no dependencies required). ## Dependencies - Access to a timbr-server -- Python from 3.9.13 or newer +- Python from 3.10 or newer ## Installation - Install as clone repository: diff --git a/pyproject.toml b/pyproject.toml index 1f0e617..c1a6f41 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,12 +34,11 @@ classifiers = [ "Intended Audience :: Developers", "Topic :: Software Development :: Build Tools", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12" ] -requires-python = ">=3.9" +requires-python = ">=3.10" dependencies = [ "requests>=2.32.4" ] From 087fbb9f4264984a70db10750ffe21f66646ab32 Mon Sep 17 00:00:00 2001 From: timbr_admin Date: Thu, 11 Dec 2025 11:00:21 +0200 Subject: [PATCH 3/5] Refactor user creation statements to include password in SQL commands and update user naming convention in tests --- test/test_user_impersonation.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/test/test_user_impersonation.py b/test/test_user_impersonation.py index 1bbe53f..1084c90 100644 --- a/test/test_user_impersonation.py +++ b/test/test_user_impersonation.py @@ -4,12 +4,12 @@ from pytimbr_api.timbr_http_connector import run_query import uuid -create_granting_user_stmt = "CREATE USER {username} OPTIONS(email='{username}@timbr-test.ai', password='{password}', first_name='{first_name}', last_name='{last_name}');" \ +create_granting_user_stmt = "CREATE USER {username} PASSWORD '{password}' OPTIONS(email='{username}@timbr-test.ai', first_name='{first_name}', last_name='{last_name}');" \ "GRANT QUERY ON ALL DATASOURCE TO USER {username};" \ "GRANT ACCESS ON ALL ONTOLOGY TO USER {username};" \ "GRANT EDIT ON ALL USER TO USER {username};" -create_impersonating_user_stmt = "CREATE USER {username} OPTIONS(email='{username}@timbr-test.ai', password='{password}', first_name='{first_name}', last_name='{last_name}');" \ +create_impersonating_user_stmt = "CREATE USER {username} PASSWORD '{password}' OPTIONS(email='{username}@timbr-test.ai', first_name='{first_name}', last_name='{last_name}');" \ "GRANT ACCESS ON ALL ONTOLOGY TO USER {username};" \ "GRANT QUERY ON ALL USER TO USER {username};" @@ -24,8 +24,10 @@ # Generate unique suffix using timestamp and UUID unique_suffix = str(uuid.uuid4())[:8] -granting_user = f"timbr_python_http_granting_user_{unique_suffix}" -impersonating_user = f"timbr_python_http_impersonating_user_{unique_suffix}" +# granting_user = f"timbr_granting_{unique_suffix}" +# impersonating_user = f"timbr_impersonating_{unique_suffix}" +granting_user = f"timbr_granting2" +impersonating_user = f"timbr_impersonating2" def create_users(test_config): print("Creating users...") @@ -120,15 +122,15 @@ def drop_users(test_config): def setup_test_users(test_config): """Fixture to create users at the start and drop them at the end""" # Setup: Create users - create_users(test_config) + # create_users(test_config) yield test_config # This provides the test_config to the test # Teardown: Drop users (runs even if tests fail) - try: - drop_users(test_config) - except Exception as e: - print(f"Warning: Failed to drop users during teardown: {e}") + # try: + # drop_users(test_config) + # except Exception as e: + # print(f"Warning: Failed to drop users during teardown: {e}") class TestUserImpersonation: def test_user_impersonation(self, setup_test_users): From ee6d93d86cb07e20c884ef959c97757b2852eaee Mon Sep 17 00:00:00 2001 From: timbr_admin Date: Thu, 11 Dec 2025 11:17:38 +0200 Subject: [PATCH 4/5] Add TIMBR_USER_PASSWORD to test configuration and update user creation statements --- test/conftest.py | 1 + test/test_user_impersonation.py | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/test/conftest.py b/test/conftest.py index 93e8c23..3a21777 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -39,4 +39,5 @@ def test_config(): "jwt_password": os.getenv("JWT_PASSWORD"), "jwt_scope": os.getenv("JWT_SCOPE"), "jwt_secret": os.getenv("JWT_SECRET"), + "timbr_user_password": os.getenv("TIMBR_USER_PASSWORD"), } diff --git a/test/test_user_impersonation.py b/test/test_user_impersonation.py index 1084c90..8f7d901 100644 --- a/test/test_user_impersonation.py +++ b/test/test_user_impersonation.py @@ -26,13 +26,13 @@ unique_suffix = str(uuid.uuid4())[:8] # granting_user = f"timbr_granting_{unique_suffix}" # impersonating_user = f"timbr_impersonating_{unique_suffix}" -granting_user = f"timbr_granting2" -impersonating_user = f"timbr_impersonating2" +granting_user = f"timbr_granting" +impersonating_user = f"timbr_impersonating" def create_users(test_config): print("Creating users...") - granting_user_stmt = create_granting_user_stmt.format(username=granting_user, password="SecurePassword123", first_name="Granting", last_name="User") - impersonating_user_stmt = create_impersonating_user_stmt.format(username=impersonating_user, password="SecurePassword123", first_name="Impersonating", last_name="User") + granting_user_stmt = create_granting_user_stmt.format(username=granting_user, password=test_config['timbr_user_password'], first_name="Granting", last_name="User") + impersonating_user_stmt = create_impersonating_user_stmt.format(username=impersonating_user, password=test_config['timbr_user_password'], first_name="Impersonating", last_name="User") run_query( url=test_config['url'], From 04eecb31ff6c07f667dff5a7551c4fe2d65afac4 Mon Sep 17 00:00:00 2001 From: timbr_admin Date: Thu, 11 Dec 2025 11:25:59 +0200 Subject: [PATCH 5/5] Enhance UTF-8 encoding support in run_query and add tests for unicode character preservation --- pytimbr_api/timbr_http_connector.py | 2 +- test/test_encoding.py | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/pytimbr_api/timbr_http_connector.py b/pytimbr_api/timbr_http_connector.py index 638d12c..b2c012e 100644 --- a/pytimbr_api/timbr_http_connector.py +++ b/pytimbr_api/timbr_http_connector.py @@ -28,7 +28,7 @@ def run_query( url: str, ontology: str, token: str, - query: str, + query: str | bytes, datasource: str = None, nested: str = 'false', verify_ssl: bool = True, diff --git a/test/test_encoding.py b/test/test_encoding.py index ee6e8c7..3b08802 100644 --- a/test/test_encoding.py +++ b/test/test_encoding.py @@ -16,6 +16,11 @@ def test_run_query_with_unicode_characters(test_config): assert results is not None, "Results should not be None for unicode query" assert len(results) > 0, "Results should contain data" + # Verify unicode characters are correctly processed in results + first_row = results[0] + assert first_row['chinese'] == '测试', "Chinese characters should be preserved" + assert first_row['french'] == 'café', "French accented characters should be preserved" + assert first_row['emoji'] == '😀', "Emoji should be preserved" def test_run_query_with_already_encoded_bytes(test_config): @@ -46,6 +51,10 @@ def test_run_query_with_special_sql_characters(test_config): assert results is not None, "Results should not be None for query with special characters" assert len(results) > 0, "Results should contain data" + # Verify special characters are correctly processed in results + first_row = results[0] + assert first_row['name'] == "O'Brien", "Apostrophe in name should be preserved" + assert first_row['copyright'] == '©', "Copyright symbol should be preserved" def test_run_query_with_multilingual_text(test_config): @@ -61,3 +70,9 @@ def test_run_query_with_multilingual_text(test_config): assert results is not None, "Results should not be None for multilingual query" assert len(results) > 0, "Results should contain data" + # Verify multilingual characters are correctly processed in results + first_row = results[0] + assert first_row['english'] == 'Hello', "English text should be preserved" + assert first_row['russian'] == 'Привет', "Russian Cyrillic text should be preserved" + assert first_row['arabic'] == 'مرحبا', "Arabic text should be preserved" + assert first_row['japanese'] == 'こんにちは', "Japanese text should be preserved"