Skip to content

Commit 26940cf

Browse files
committed
style + compacting the changelog
1 parent 81da8a8 commit 26940cf

3 files changed

Lines changed: 56 additions & 41 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 12 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ Also, the RFC6764 discovery may not always be robust, causing fallbacks and henc
4141
### Deprecations
4242

4343
* `Event.expand_rrule` will be removed in some future release, unless someone protests.
44-
* `Event.split_expanded` too. Both of them were used internally, now it's not. It's dead code, most lkely nobody and nothing is using them.
44+
* `Event.split_expanded` too. Both of them were used internally, now it's not. It's dead code, most likely nobody and nothing is using them.
4545

4646
### Changed
4747

@@ -50,30 +50,25 @@ Also, the RFC6764 discovery may not always be robust, causing fallbacks and henc
5050

5151
### Added
5252

53-
* **Automated Baikal Docker testing framework**: Complete automated testing setup for Baikal CalDAV server (v0.10.1) using Docker containers. The framework automatically detects docker-compose availability, starts containers when needed, and gracefully skips tests when Docker is unavailable. Works seamlessly in both local development and CI/CD environments (GitHub Actions).
54-
- Automatic container lifecycle management (setup/teardown)
55-
- Pre-configured database with test user credentials
56-
- Auto-appends `/dav.php` to base URL for correct CalDAV endpoint
57-
- Uses official Baikal 0.10.1 SQLite schema with all required tables
58-
- YAML configuration support for Baikal 0.7.0+ compatibility
59-
- Graceful degradation on systems without Docker
60-
- Updated compatibility hints for Baikal 0.10.1
61-
* **RFC 6764 DNS-based service discovery**: Automatic CalDAV/CardDAV service discovery using DNS SRV/TXT records and well-known URIs. Users can now provide just a domain name or email address (e.g., `DAVClient(username='user@example.com')`) and the library will automatically discover the CalDAV service endpoint. The discovery process follows RFC 6764 specification. This involves a new required dependency: `dnspython` for DNS queries. DNS-based discovery can be disabled in the davclient connection settings, but I've opted against implementing a fallback if the dns library is not installed.
62-
- **SECURITY**: DNS-based discovery has security implications. By default, `require_tls=True` prevents downgrade attacks by only accepting HTTPS connections. See security documentation for details.
53+
* **New ways to configure the client connection, new parameters**
54+
- **RFC 6764 DNS-based service discovery**: Automatic CalDAV/CardDAV service discovery using DNS SRV/TXT records and well-known URIs. Users can now provide just a domain name or email address (e.g., `DAVClient(username='user@example.com')`) and the library will automatically discover the CalDAV service endpoint. The discovery process follows RFC 6764 specification. This involves a new required dependency: `dnspython` for DNS queries. DNS-based discovery can be disabled in the davclient connection settings, but I've opted against implementing a fallback if the dns library is not installed.
6355
- New `require_tls` parameter (default: `True`) prevents DNS-based downgrade attacks
64-
- Username extraction from email addresses (`user@example.com` → username: `user`)
65-
- Discovery from username parameter when URL is omitted
66-
* The client connection parameter `features` may now simply be a string label referencing a well-known server or cloud solution - like `features: posteo`. https://github.com/python-caldav/caldav/pull/561
67-
* The client connection parameter `url` is no longer needed when referencing a well-known cloud solution. https://github.com/python-caldav/caldav/pull/561
68-
* The client connection parameter `url` may contain just the domain name (without any slashes) and the URL will be constructed, if referencing a well-known caldav server implementation. https://github.com/python-caldav/caldav/pull/561
69-
* New interface for searches. `mysearcher = caldav.CalDAVSearcher(...) ; mysearcher.add_property_filter(...) ; mysearcher.search(calendar)`. May be useful for complicated searches.
56+
- The client connection parameter `features` may now simply be a string label referencing a well-known server or cloud solution - like `features: posteo`. https://github.com/python-caldav/caldav/pull/561
57+
- The client connection parameter `url` is no longer needed when referencing a well-known cloud solution. https://github.com/python-caldav/caldav/pull/561
58+
* The client connection parameter `url` may contain just the domain name (without any slashes). It may then either look up the URL path in the known caldav server database, or through RFC6764
59+
* **New interface for searches** `mysearcher = caldav.CalDAVSearcher(...) ; mysearcher.add_property_filter(...) ; mysearcher.search(calendar)`. It's a bit harder to use, but opens up the possibility to do more complicated searches.
7060
* **Collation support for CalDAV text-match queries (RFC 4791 § 9.7.5)**: CalDAV searches now properly pass collation attributes to the server, enabling case-insensitive searches and Unicode-aware text matching. The `CalDAVSearcher.add_property_filter()` method now accepts `case_sensitive` and `collation` parameters. Supported collations include:
7161
- `i;octet` (case-sensitive, binary comparison) - default
7262
- `i;ascii-casemap` (case-insensitive for ASCII characters, RFC 4790)
7363
- `i;unicode-casemap` (Unicode case-insensitive, RFC 5051 - server support may vary)
7464
* Client-side filtering method: `CalDAVSearcher.filter()` provides comprehensive client-side filtering, expansion, and sorting of calendar objects with full timezone preservation support.
7565
* Example code: New `examples/collation_usage.py` demonstrates case-sensitive and case-insensitive calendar searches.
7666

67+
### Test suite
68+
69+
* **Automated Baikal Docker testing framework** using Docker containers. Will only run if docker is available.
70+
* Since the new search code now can work around different server quirks, quite some of the test code has been simplified. Many cases of "make a search, if server supports this, then assert correct number of events returned" could be collapsed to "make a search, then assert correct number of events returned" - meaning that **the library is tested rather than the server**.
71+
7772
## [2.1.2] - [2025-11-08]
7873

7974
Version 2.1.0 comes without niquests in the dependency file. Version 2.1.2 come with niquests in the dependency file. Also fixed up some minor mistakes in the CHANGELOG.

‎tests/conf.py‎

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,11 @@
113113
timeout=5,
114114
)
115115
test_baikal = True
116-
except (subprocess.CalledProcessError, FileNotFoundError, subprocess.TimeoutExpired):
116+
except (
117+
subprocess.CalledProcessError,
118+
FileNotFoundError,
119+
subprocess.TimeoutExpired,
120+
):
117121
test_baikal = False
118122

119123
#####################
@@ -280,12 +284,16 @@ def silly_request():
280284
import subprocess
281285
from pathlib import Path
282286

283-
baikal_base_url = os.environ.get("BAIKAL_URL", f"http://{baikal_host}:{baikal_port}")
287+
baikal_base_url = os.environ.get(
288+
"BAIKAL_URL", f"http://{baikal_host}:{baikal_port}"
289+
)
284290
# Ensure the URL includes /dav.php/ for CalDAV endpoint
285-
if not baikal_base_url.endswith('/dav.php') and not baikal_base_url.endswith('/dav.php/'):
291+
if not baikal_base_url.endswith("/dav.php") and not baikal_base_url.endswith(
292+
"/dav.php/"
293+
):
286294
baikal_url = f"{baikal_base_url}/dav.php"
287295
else:
288-
baikal_url = baikal_base_url.rstrip('/')
296+
baikal_url = baikal_base_url.rstrip("/")
289297

290298
baikal_username = os.environ.get("BAIKAL_USERNAME", "testuser")
291299
baikal_password = os.environ.get("BAIKAL_PASSWORD", "testpass")
@@ -313,7 +321,11 @@ def setup_baikal(self) -> None:
313321
check=True,
314322
timeout=5,
315323
)
316-
except (subprocess.CalledProcessError, FileNotFoundError, subprocess.TimeoutExpired) as e:
324+
except (
325+
subprocess.CalledProcessError,
326+
FileNotFoundError,
327+
subprocess.TimeoutExpired,
328+
) as e:
317329
raise RuntimeError(
318330
"docker-compose is not available. Baikal tests require Docker. "
319331
"Please install Docker or skip Baikal tests by setting "
@@ -325,9 +337,7 @@ def setup_baikal(self) -> None:
325337

326338
# Check if docker-compose.yml exists
327339
if not (baikal_dir / "docker-compose.yml").exists():
328-
raise FileNotFoundError(
329-
f"docker-compose.yml not found in {baikal_dir}"
330-
)
340+
raise FileNotFoundError(f"docker-compose.yml not found in {baikal_dir}")
331341

332342
# Start the container but don't wait for full startup
333343
print(f"Starting Baikal container from {baikal_dir}...")
@@ -345,15 +355,25 @@ def setup_baikal(self) -> None:
345355
config_dir = baikal_dir / "config"
346356

347357
subprocess.run(
348-
["docker", "cp", f"{specific_dir}/.", "baikal-test:/var/www/baikal/Specific/"],
358+
[
359+
"docker",
360+
"cp",
361+
f"{specific_dir}/.",
362+
"baikal-test:/var/www/baikal/Specific/",
363+
],
349364
check=True,
350365
capture_output=True,
351366
)
352367

353368
# Copy YAML config for newer Baikal versions
354369
if config_dir.exists():
355370
subprocess.run(
356-
["docker", "cp", f"{config_dir}/.", "baikal-test:/var/www/baikal/config/"],
371+
[
372+
"docker",
373+
"cp",
374+
f"{config_dir}/.",
375+
"baikal-test:/var/www/baikal/config/",
376+
],
357377
check=True,
358378
capture_output=True,
359379
)
@@ -379,9 +399,7 @@ def setup_baikal(self) -> None:
379399
pass
380400
time.sleep(1)
381401

382-
raise TimeoutError(
383-
f"Baikal did not become ready after {max_attempts} seconds"
384-
)
402+
raise TimeoutError(f"Baikal did not become ready after {max_attempts} seconds")
385403

386404
def teardown_baikal(self) -> None:
387405
"""Stop Baikal Docker container."""

‎tests/docker-test-servers/baikal/create_baikal_db.py‎

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,15 @@
1010
Usage:
1111
python create_baikal_db.py
1212
"""
13-
1413
import hashlib
1514
import os
1615
import sqlite3
1716
from pathlib import Path
1817

1918

20-
def create_baikal_db(db_path: Path, username: str = "testuser", password: str = "testpass") -> None:
19+
def create_baikal_db(
20+
db_path: Path, username: str = "testuser", password: str = "testpass"
21+
) -> None:
2122
"""Create a Baikal SQLite database with a test user using official schema."""
2223

2324
# Ensure directory exists
@@ -192,21 +193,20 @@ def create_baikal_db(db_path: Path, username: str = "testuser", password: str =
192193
ha1 = hashlib.md5(f"{username}:{realm}:{password}".encode()).hexdigest()
193194

194195
cursor.execute(
195-
"INSERT INTO users (username, digesta1) VALUES (?, ?)",
196-
(username, ha1)
196+
"INSERT INTO users (username, digesta1) VALUES (?, ?)", (username, ha1)
197197
)
198198

199199
# Create principal for user
200200
principal_uri = f"principals/{username}"
201201
cursor.execute(
202202
"INSERT INTO principals (uri, email, displayname) VALUES (?, ?, ?)",
203-
(principal_uri, f"{username}@baikal.test", f"Test User ({username})")
203+
(principal_uri, f"{username}@baikal.test", f"Test User ({username})"),
204204
)
205205

206206
# Create default calendar
207207
cursor.execute(
208208
"INSERT INTO calendars (synctoken, components) VALUES (?, ?)",
209-
(1, "VEVENT,VTODO,VJOURNAL")
209+
(1, "VEVENT,VTODO,VJOURNAL"),
210210
)
211211
calendar_id = cursor.lastrowid
212212

@@ -215,15 +215,15 @@ def create_baikal_db(db_path: Path, username: str = "testuser", password: str =
215215
"""INSERT INTO calendarinstances
216216
(calendarid, principaluri, access, displayname, uri, calendarorder, calendarcolor)
217217
VALUES (?, ?, ?, ?, ?, ?, ?)""",
218-
(calendar_id, principal_uri, 1, "Default Calendar", "default", 0, "#3a87ad")
218+
(calendar_id, principal_uri, 1, "Default Calendar", "default", 0, "#3a87ad"),
219219
)
220220

221221
# Create default addressbook for user
222222
cursor.execute(
223223
"""INSERT INTO addressbooks
224224
(principaluri, displayname, uri, synctoken)
225225
VALUES (?, ?, ?, ?)""",
226-
(principal_uri, "Default Address Book", "default", 1)
226+
(principal_uri, "Default Address Book", "default", 1),
227227
)
228228

229229
conn.commit()
@@ -353,7 +353,9 @@ def create_baikal_yaml(yaml_path: Path) -> None:
353353
mysql_dbname: 'baikal'
354354
mysql_username: 'baikal'
355355
mysql_password: 'baikal'
356-
""".format(admin_hash=admin_hash)
356+
""".format(
357+
admin_hash=admin_hash
358+
)
357359

358360
yaml_path.write_text(yaml_content)
359361
print(f"✓ Created Baikal YAML config at {yaml_path}")
@@ -377,9 +379,9 @@ def create_baikal_yaml(yaml_path: Path) -> None:
377379
yaml_path = script_dir / "config" / "baikal.yaml"
378380
create_baikal_yaml(yaml_path)
379381

380-
print("\n" + "="*70)
382+
print("\n" + "=" * 70)
381383
print("Baikal pre-configuration complete!")
382-
print("="*70)
384+
print("=" * 70)
383385
print("\nYou can now start Baikal with: docker-compose up -d")
384386
print("\nCredentials:")
385387
print(" Admin: admin / admin")

0 commit comments

Comments
 (0)