Skip to content

Commit a63b7c8

Browse files
committed
Enhance vCon party management and documentation
- Updated the parties property to return a list of dictionaries instead of Party objects, allowing for in-place modifications. - Introduced a new method, get_party_objects(), to retrieve Party objects from the internal data structure. - Enhanced usage documentation to reflect changes in party data access and provide examples for both dictionary and object usage. - Updated tests to align with the new party data structure and ensure correct assertions.
1 parent d86791b commit a63b7c8

File tree

5 files changed

+299
-6
lines changed

5 files changed

+299
-6
lines changed

MUTABILITY_FIX_SUMMARY.md

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
# Mutability Consistency Fix Summary
2+
3+
## Problem Description
4+
5+
There was an inconsistency in the mutability of objects returned by `vcon.dialog` and `vcon.parties`:
6+
7+
- **`vcon.dialog`** returned a list of dictionaries from the internal data structure, allowing users to modify dialog entries in place.
8+
- **`vcon.parties`** returned newly constructed Party objects from the underlying data, making in-place modifications ineffective since changes were not reflected in the internal vcon_dict.
9+
10+
This inconsistency was confusing for users who expected both properties to behave the same way.
11+
12+
## Solution Implemented
13+
14+
**Approach**: Return references to mutable objects in both cases.
15+
16+
### Changes Made
17+
18+
1. **Modified `vcon.parties` property** (lines 1152-1169 in `src/vcon/vcon.py`):
19+
- Changed return type from `List[Party]` to `List[Dict[str, Any]]`
20+
- Changed implementation from `[Party(**party) for party in self.vcon_dict.get("parties", [])]` to `self.vcon_dict.get("parties", [])`
21+
- Updated documentation to reflect the new behavior
22+
23+
2. **Added `get_party_objects()` method** (lines 1171-1187 in `src/vcon/vcon.py`):
24+
- Provides backward compatibility for code that needs Party objects
25+
- Returns `[Party(**party) for party in self.vcon_dict.get("parties", [])]`
26+
- Includes comprehensive documentation and examples
27+
28+
3. **Updated `vcon.dialog` property documentation** (lines 1189-1206 in `src/vcon/vcon.py`):
29+
- Made the mutability behavior explicit in the documentation
30+
- Added examples showing in-place modifications
31+
32+
4. **Fixed existing tests** (lines 286-297 in `tests/test_vcon.py`):
33+
- Updated `test_properties()` to work with the new dictionary-based approach
34+
- Changed from `vcon.parties[0].to_dict()` to `vcon.parties[0]`
35+
36+
## Benefits
37+
38+
1. **Consistency**: Both `vcon.dialog` and `vcon.parties` now return mutable references
39+
2. **Performance**: No longer creates new objects on every access
40+
3. **User expectations**: Users can modify objects in place as expected
41+
4. **Backward compatibility**: Existing code can use `get_party_objects()` when Party objects are needed
42+
43+
## Usage Examples
44+
45+
### Before (Inconsistent)
46+
```python
47+
# This worked - dialog was mutable
48+
vcon.dialog[0]["body"] = "Modified content"
49+
50+
# This didn't work - parties were not mutable
51+
vcon.parties[0].name = "Modified name" # Changes lost
52+
```
53+
54+
### After (Consistent)
55+
```python
56+
# Both work - both are mutable
57+
vcon.dialog[0]["body"] = "Modified content"
58+
vcon.parties[0]["name"] = "Modified name"
59+
60+
# Both changes are reflected in the internal data
61+
assert vcon.dialog[0]["body"] == "Modified content"
62+
assert vcon.parties[0]["name"] == "Modified name"
63+
```
64+
65+
### Backward Compatibility
66+
```python
67+
# For code that needs Party objects
68+
party_objects = vcon.get_party_objects()
69+
party_objects[0].name = "Modified name" # This creates new objects
70+
```
71+
72+
## Testing
73+
74+
- Created comprehensive tests in `tests/test_mutability_consistency.py`
75+
- Updated existing tests to work with the new behavior
76+
- Verified core mutability logic works correctly
77+
- All tests pass with the new implementation
78+
79+
## Breaking Changes
80+
81+
- **`vcon.parties`** now returns `List[Dict[str, Any]]` instead of `List[Party]`
82+
- Code that accessed `vcon.parties[0].name` needs to be updated to `vcon.parties[0]["name"]`
83+
- Code that needs Party objects should use `vcon.get_party_objects()`
84+
85+
## Migration Guide
86+
87+
For existing code that uses Party objects:
88+
89+
```python
90+
# Old way (no longer works)
91+
parties = vcon.parties
92+
for party in parties:
93+
print(party.name)
94+
95+
# New way 1: Use dictionary access
96+
parties = vcon.parties
97+
for party in parties:
98+
print(party["name"])
99+
100+
# New way 2: Use get_party_objects() for backward compatibility
101+
party_objects = vcon.get_party_objects()
102+
for party in party_objects:
103+
print(party.name)
104+
```
105+
106+
## Files Modified
107+
108+
1. `src/vcon/vcon.py` - Main implementation changes
109+
2. `tests/test_vcon.py` - Updated existing tests
110+
3. `tests/test_mutability_consistency.py` - New comprehensive tests
111+
4. `MUTABILITY_FIX_SUMMARY.md` - This documentation
112+
113+
The fix successfully resolves the mutability inconsistency while maintaining backward compatibility through the new `get_party_objects()` method.

docs/source/usage.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,14 @@ To read an existing vCon container:
7878
# Get participants with enhanced information
7979
parties = vcon.parties
8080
for party in parties:
81+
print(f"Name: {party['name']}")
82+
print(f"SIP: {party.get('sip', 'Not set')}")
83+
print(f"DID: {party.get('did', 'Not set')}")
84+
print(f"Timezone: {party.get('timezone', 'Not set')}")
85+
86+
# Alternative: Use get_party_objects() for Party object access
87+
party_objects = vcon.get_party_objects()
88+
for party in party_objects:
8189
print(f"Name: {party.name}")
8290
print(f"SIP: {getattr(party, 'sip', 'Not set')}")
8391
print(f"DID: {getattr(party, 'did', 'Not set')}")

src/vcon/vcon.py

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1150,18 +1150,39 @@ def dumps(self) -> str:
11501150
return self.to_json()
11511151

11521152
@property
1153-
def parties(self) -> List[Party]:
1153+
def parties(self) -> List[Dict[str, Any]]:
11541154
"""
11551155
Get the list of parties in the vCon.
11561156
11571157
Returns:
1158-
A list of Party objects representing all participants in the conversation
1158+
A list of party dictionaries representing all participants in the conversation.
1159+
These are mutable references to the internal data structure, allowing in-place modifications.
11591160
11601161
Example:
11611162
>>> vcon = Vcon.build_new()
11621163
>>> vcon.add_party(Party(type="person", name="John Doe"))
11631164
>>> parties = vcon.parties
1164-
>>> print(parties[0].name) # Prints "John Doe"
1165+
>>> print(parties[0]["name"]) # Prints "John Doe"
1166+
>>> parties[0]["name"] = "Jane Doe" # In-place modification works
1167+
>>> print(vcon.parties[0]["name"]) # Prints "Jane Doe"
1168+
"""
1169+
return self.vcon_dict.get("parties", [])
1170+
1171+
def get_party_objects(self) -> List[Party]:
1172+
"""
1173+
Get the list of parties as Party objects.
1174+
1175+
This method creates Party objects from the underlying dictionary data.
1176+
Use this when you need Party objects instead of raw dictionaries.
1177+
1178+
Returns:
1179+
A list of Party objects representing all participants in the conversation
1180+
1181+
Example:
1182+
>>> vcon = Vcon.build_new()
1183+
>>> vcon.add_party(Party(type="person", name="John Doe"))
1184+
>>> party_objects = vcon.get_party_objects()
1185+
>>> print(party_objects[0].name) # Prints "John Doe"
11651186
"""
11661187
return [Party(**party) for party in self.vcon_dict.get("parties", [])]
11671188

@@ -1171,13 +1192,16 @@ def dialog(self) -> List[Dict[str, Any]]:
11711192
Get the list of dialog entries in the vCon.
11721193
11731194
Returns:
1174-
A list of dialog entries representing the conversation content
1195+
A list of dialog dictionaries representing the conversation content.
1196+
These are mutable references to the internal data structure, allowing in-place modifications.
11751197
11761198
Example:
11771199
>>> vcon = Vcon.build_new()
11781200
>>> vcon.add_dialog(Dialog(type="text", start="2023-01-01T00:00:00Z", parties=[0]))
11791201
>>> dialog = vcon.dialog
11801202
>>> print(dialog[0]["type"]) # Prints "text"
1203+
>>> dialog[0]["body"] = "Modified content" # In-place modification works
1204+
>>> print(vcon.dialog[0]["body"]) # Prints "Modified content"
11811205
"""
11821206
return self.vcon_dict.get("dialog", [])
11831207

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
"""
2+
Test to demonstrate and verify the mutability consistency between vcon.dialog and vcon.parties properties.
3+
"""
4+
import pytest
5+
from vcon import Vcon
6+
from vcon.party import Party
7+
from vcon.dialog import Dialog
8+
from datetime import datetime, timezone
9+
10+
11+
def test_mutability_consistency_demonstration():
12+
"""
13+
Demonstrate that both vcon.dialog and vcon.parties now work consistently.
14+
15+
This test shows that:
16+
- vcon.dialog returns mutable references (in-place modifications work)
17+
- vcon.parties now also returns mutable references (in-place modifications work)
18+
"""
19+
# Create a vCon with parties and dialogs
20+
vcon = Vcon.build_new()
21+
22+
# Add parties
23+
party1 = Party(name="Alice", tel="+1234567890")
24+
party2 = Party(name="Bob", tel="+1987654321")
25+
vcon.add_party(party1)
26+
vcon.add_party(party2)
27+
28+
# Add dialogs
29+
dialog1 = Dialog(
30+
type="text",
31+
start=datetime.now(timezone.utc).isoformat(),
32+
parties=[0, 1],
33+
body="Hello, this is a test message"
34+
)
35+
dialog2 = Dialog(
36+
type="text",
37+
start=datetime.now(timezone.utc).isoformat(),
38+
parties=[0],
39+
body="This is another message"
40+
)
41+
vcon.add_dialog(dialog1)
42+
vcon.add_dialog(dialog2)
43+
44+
# Test dialog mutability (should work - returns direct references)
45+
dialog_list = vcon.dialog
46+
original_body = dialog_list[0]["body"]
47+
dialog_list[0]["body"] = "Modified dialog body"
48+
49+
# Verify the change is reflected in the internal data
50+
assert vcon.dialog[0]["body"] == "Modified dialog body"
51+
assert vcon.vcon_dict["dialog"][0]["body"] == "Modified dialog body"
52+
53+
# Test parties mutability (now works - returns mutable references)
54+
parties_list = vcon.parties
55+
original_name = parties_list[0]["name"]
56+
57+
# This modification now affects the internal data
58+
parties_list[0]["name"] = "Modified Alice"
59+
60+
# Verify the change IS reflected in the internal data (new consistent behavior)
61+
assert vcon.parties[0]["name"] == "Modified Alice" # Modified name
62+
assert vcon.vcon_dict["parties"][0]["name"] == "Modified Alice" # Internal data changed
63+
64+
65+
def test_mutability_consistency_after_fix():
66+
"""
67+
Test that after the fix, both vcon.dialog and vcon.parties behave consistently.
68+
69+
This test verifies that both properties return mutable references.
70+
"""
71+
# Create a vCon with parties and dialogs
72+
vcon = Vcon.build_new()
73+
74+
# Add parties
75+
party1 = Party(name="Alice", tel="+1234567890")
76+
party2 = Party(name="Bob", tel="+1987654321")
77+
vcon.add_party(party1)
78+
vcon.add_party(party2)
79+
80+
# Add dialogs
81+
dialog1 = Dialog(
82+
type="text",
83+
start=datetime.now(timezone.utc).isoformat(),
84+
parties=[0, 1],
85+
body="Hello, this is a test message"
86+
)
87+
vcon.add_dialog(dialog1)
88+
89+
# Test dialog mutability (should work)
90+
dialog_list = vcon.dialog
91+
dialog_list[0]["body"] = "Modified dialog body"
92+
assert vcon.dialog[0]["body"] == "Modified dialog body"
93+
assert vcon.vcon_dict["dialog"][0]["body"] == "Modified dialog body"
94+
95+
# Test parties mutability (should work after fix)
96+
parties_list = vcon.parties
97+
parties_list[0]["name"] = "Modified Alice"
98+
99+
# Verify the change IS reflected in the internal data (after fix)
100+
assert vcon.parties[0]["name"] == "Modified Alice"
101+
assert vcon.vcon_dict["parties"][0]["name"] == "Modified Alice"
102+
103+
# Test that we can still access party data as dictionaries
104+
assert isinstance(vcon.parties[0], dict)
105+
assert "name" in vcon.parties[0]
106+
assert "tel" in vcon.parties[0]
107+
108+
109+
def test_party_object_creation_still_works():
110+
"""
111+
Test that we can still create Party objects from the dictionary data when needed.
112+
"""
113+
vcon = Vcon.build_new()
114+
115+
# Add a party
116+
party = Party(name="Alice", tel="+1234567890")
117+
vcon.add_party(party)
118+
119+
# Get the party data as dictionary
120+
party_dict = vcon.parties[0]
121+
122+
# Create a Party object from the dictionary data
123+
party_obj = Party(**party_dict)
124+
125+
# Verify the Party object works correctly
126+
assert party_obj.name == "Alice"
127+
assert party_obj.tel == "+1234567890"
128+
assert isinstance(party_obj, Party)
129+
130+
131+
def test_backward_compatibility():
132+
"""
133+
Test that existing code that expects Party objects still works.
134+
"""
135+
vcon = Vcon.build_new()
136+
137+
# Add a party
138+
party = Party(name="Alice", tel="+1234567890")
139+
vcon.add_party(party)
140+
141+
# Get parties and create Party objects (common pattern)
142+
parties = [Party(**party_dict) for party_dict in vcon.parties]
143+
144+
# Verify the Party objects work correctly
145+
assert len(parties) == 1
146+
assert parties[0].name == "Alice"
147+
assert parties[0].tel == "+1234567890"
148+
assert isinstance(parties[0], Party)

tests/test_vcon.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -283,13 +283,13 @@ def test_properties() -> None:
283283
assert vcon.created_at == "2024-10-20T15:02:55.490850+00:00"
284284
assert len(vcon.parties) == 2
285285

286-
assert vcon.parties[0].to_dict() == {
286+
assert vcon.parties[0] == {
287287
"tel": "+14513886516",
288288
"mailto": "david.scott@pickrandombusinesstype.com",
289289
"name": "David Scott",
290290
"meta": {"role": "agent"},
291291
}
292-
assert vcon.parties[1].to_dict() == {
292+
assert vcon.parties[1] == {
293293
"tel": "+16171557264",
294294
"mailto": "diane.allen@gmail.com",
295295
"name": "Diane Allen",

0 commit comments

Comments
 (0)