v2kit 0.4__tar.gz → 0.6__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. {v2kit-0.4 → v2kit-0.6}/CHANGELOG.md +31 -1
  2. {v2kit-0.4 → v2kit-0.6}/PKG-INFO +36 -6
  3. {v2kit-0.4 → v2kit-0.6}/README.md +2 -2
  4. {v2kit-0.4 → v2kit-0.6}/SECURITY.md +2 -2
  5. {v2kit-0.4 → v2kit-0.6}/setup.py +3 -3
  6. {v2kit-0.4 → v2kit-0.6}/tests/test_errors.py +30 -3
  7. {v2kit-0.4 → v2kit-0.6}/tests/test_functions.py +9 -1
  8. {v2kit-0.4 → v2kit-0.6}/tests/test_http.py +75 -1
  9. {v2kit-0.4 → v2kit-0.6}/tests/test_shadowsocks.py +88 -2
  10. {v2kit-0.4 → v2kit-0.6}/tests/test_socks.py +79 -1
  11. {v2kit-0.4 → v2kit-0.6}/tests/test_trojan.py +88 -1
  12. {v2kit-0.4 → v2kit-0.6}/tests/test_vless.py +83 -1
  13. {v2kit-0.4 → v2kit-0.6}/tests/test_vmess.py +117 -1
  14. {v2kit-0.4 → v2kit-0.6}/v2kit/functions.py +14 -35
  15. {v2kit-0.4 → v2kit-0.6}/v2kit/models/base.py +67 -15
  16. {v2kit-0.4 → v2kit-0.6}/v2kit/models/http.py +7 -18
  17. {v2kit-0.4 → v2kit-0.6}/v2kit/models/shadowsocks.py +11 -30
  18. {v2kit-0.4 → v2kit-0.6}/v2kit/models/socks.py +9 -27
  19. {v2kit-0.4 → v2kit-0.6}/v2kit/models/trojan.py +8 -22
  20. {v2kit-0.4 → v2kit-0.6}/v2kit/models/vless.py +8 -22
  21. {v2kit-0.4 → v2kit-0.6}/v2kit/models/vmess.py +10 -33
  22. {v2kit-0.4 → v2kit-0.6}/v2kit/params.py +15 -3
  23. {v2kit-0.4 → v2kit-0.6}/v2kit/parsers.py +64 -78
  24. {v2kit-0.4 → v2kit-0.6}/v2kit/utils.py +6 -9
  25. {v2kit-0.4 → v2kit-0.6}/v2kit/validators.py +7 -13
  26. {v2kit-0.4 → v2kit-0.6}/v2kit.egg-info/PKG-INFO +36 -6
  27. {v2kit-0.4 → v2kit-0.6}/AUTHORS.md +0 -0
  28. {v2kit-0.4 → v2kit-0.6}/LICENSE +0 -0
  29. {v2kit-0.4 → v2kit-0.6}/MANIFEST.in +0 -0
  30. {v2kit-0.4 → v2kit-0.6}/requirements-dev.txt +0 -0
  31. {v2kit-0.4 → v2kit-0.6}/setup.cfg +0 -0
  32. {v2kit-0.4 → v2kit-0.6}/v2kit/__init__.py +0 -0
  33. {v2kit-0.4 → v2kit-0.6}/v2kit/__main__.py +0 -0
  34. {v2kit-0.4 → v2kit-0.6}/v2kit/errors.py +0 -0
  35. {v2kit-0.4 → v2kit-0.6}/v2kit/models/__init__.py +0 -0
  36. {v2kit-0.4 → v2kit-0.6}/v2kit.egg-info/SOURCES.txt +0 -0
  37. {v2kit-0.4 → v2kit-0.6}/v2kit.egg-info/dependency_links.txt +0 -0
  38. {v2kit-0.4 → v2kit-0.6}/v2kit.egg-info/top_level.txt +0 -0
@@ -5,6 +5,34 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
5
5
  and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
6
6
 
7
7
  ## [Unreleased]
8
+ ## [0.6] - 2026-09-01
9
+ ### Added
10
+ - `clear_extra` method
11
+ - `set_extra_item` method
12
+ - `get_extra_item` method
13
+ - `remove_extra_item` method
14
+ ### Changed
15
+ - `_get_protocol` function modified
16
+ - `decode_subscription` function blank line bug fixed
17
+ - Port validation bug fixed
18
+ - AlterId validation bug fixed
19
+ - Test system modified
20
+ - Parsers updated
21
+ ## [0.5] - 2026-07-31
22
+ ### Added
23
+ - `encoded_label` property
24
+ - `uri_label` property
25
+ ### Changed
26
+ - `encode_subscription` function modified
27
+ - `parse` function modified
28
+ - `extra` property modified
29
+ - `update_extra` method modified
30
+ - `to_uri` method modified
31
+ - `to_dict` method modified
32
+ - `Protocol` enum updated
33
+ - Scheme bug fixed
34
+ - VMESS parser bug fixed
35
+ - Test system modified
8
36
  ## [0.4] - 2026-07-02
9
37
  ### Added
10
38
  - `HttpConfig` class
@@ -50,7 +78,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
50
78
  - `encode_subscription` function
51
79
  - `decode_subscription` function
52
80
 
53
- [Unreleased]: https://github.com/sepandhaghighi/v2kit/compare/v0.4...dev
81
+ [Unreleased]: https://github.com/sepandhaghighi/v2kit/compare/v0.6...dev
82
+ [0.6]: https://github.com/sepandhaghighi/v2kit/compare/v0.5...v0.6
83
+ [0.5]: https://github.com/sepandhaghighi/v2kit/compare/v0.4...v0.5
54
84
  [0.4]: https://github.com/sepandhaghighi/v2kit/compare/v0.3...v0.4
55
85
  [0.3]: https://github.com/sepandhaghighi/v2kit/compare/v0.2...v0.3
56
86
  [0.2]: https://github.com/sepandhaghighi/v2kit/compare/v0.1...v0.2
@@ -1,15 +1,15 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: v2kit
3
- Version: 0.4
3
+ Version: 0.6
4
4
  Summary: V2Kit: A Lightweight Toolkit for V2Ray Config Manipulation
5
5
  Home-page: https://github.com/sepandhaghighi/v2kit
6
- Download-URL: https://github.com/sepandhaghighi/v2kit/tarball/v0.4
6
+ Download-URL: https://github.com/sepandhaghighi/v2kit/tarball/v0.6
7
7
  Author: Sepand Haghighi
8
8
  Author-email: me@sepand.tech
9
9
  License: MIT
10
10
  Project-URL: Source, https://github.com/sepandhaghighi/v2kit
11
11
  Keywords: v2ray v2ray-config v2ray-tools vmess vless trojan shadowsocks proxy subscription network
12
- Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Development Status :: 4 - Beta
13
13
  Classifier: Natural Language :: English
14
14
  Classifier: License :: OSI Approved :: MIT License
15
15
  Classifier: Operating System :: OS Independent
@@ -96,13 +96,13 @@ V2Kit is a lightweight and extensible Python toolkit for working with V2Ray prox
96
96
  ## Installation
97
97
 
98
98
  ### Source Code
99
- - Download [Version 0.4](https://github.com/sepandhaghighi/v2kit/archive/v0.4.zip) or [Latest Source](https://github.com/sepandhaghighi/v2kit/archive/dev.zip)
99
+ - Download [Version 0.6](https://github.com/sepandhaghighi/v2kit/archive/v0.6.zip) or [Latest Source](https://github.com/sepandhaghighi/v2kit/archive/dev.zip)
100
100
  - `pip install .`
101
101
 
102
102
  ### PyPI
103
103
 
104
104
  - Check [Python Packaging User Guide](https://packaging.python.org/installing/)
105
- - `pip install v2kit==0.4`
105
+ - `pip install v2kit==0.6`
106
106
 
107
107
  ## Supported Protocols
108
108
 
@@ -211,6 +211,34 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
211
211
  and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
212
212
 
213
213
  ## [Unreleased]
214
+ ## [0.6] - 2026-09-01
215
+ ### Added
216
+ - `clear_extra` method
217
+ - `set_extra_item` method
218
+ - `get_extra_item` method
219
+ - `remove_extra_item` method
220
+ ### Changed
221
+ - `_get_protocol` function modified
222
+ - `decode_subscription` function blank line bug fixed
223
+ - Port validation bug fixed
224
+ - AlterId validation bug fixed
225
+ - Test system modified
226
+ - Parsers updated
227
+ ## [0.5] - 2026-07-31
228
+ ### Added
229
+ - `encoded_label` property
230
+ - `uri_label` property
231
+ ### Changed
232
+ - `encode_subscription` function modified
233
+ - `parse` function modified
234
+ - `extra` property modified
235
+ - `update_extra` method modified
236
+ - `to_uri` method modified
237
+ - `to_dict` method modified
238
+ - `Protocol` enum updated
239
+ - Scheme bug fixed
240
+ - VMESS parser bug fixed
241
+ - Test system modified
214
242
  ## [0.4] - 2026-07-02
215
243
  ### Added
216
244
  - `HttpConfig` class
@@ -256,7 +284,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
256
284
  - `encode_subscription` function
257
285
  - `decode_subscription` function
258
286
 
259
- [Unreleased]: https://github.com/sepandhaghighi/v2kit/compare/v0.4...dev
287
+ [Unreleased]: https://github.com/sepandhaghighi/v2kit/compare/v0.6...dev
288
+ [0.6]: https://github.com/sepandhaghighi/v2kit/compare/v0.5...v0.6
289
+ [0.5]: https://github.com/sepandhaghighi/v2kit/compare/v0.4...v0.5
260
290
  [0.4]: https://github.com/sepandhaghighi/v2kit/compare/v0.3...v0.4
261
291
  [0.3]: https://github.com/sepandhaghighi/v2kit/compare/v0.2...v0.3
262
292
  [0.2]: https://github.com/sepandhaghighi/v2kit/compare/v0.1...v0.2
@@ -51,13 +51,13 @@ V2Kit is a lightweight and extensible Python toolkit for working with V2Ray prox
51
51
  ## Installation
52
52
 
53
53
  ### Source Code
54
- - Download [Version 0.4](https://github.com/sepandhaghighi/v2kit/archive/v0.4.zip) or [Latest Source](https://github.com/sepandhaghighi/v2kit/archive/dev.zip)
54
+ - Download [Version 0.6](https://github.com/sepandhaghighi/v2kit/archive/v0.6.zip) or [Latest Source](https://github.com/sepandhaghighi/v2kit/archive/dev.zip)
55
55
  - `pip install .`
56
56
 
57
57
  ### PyPI
58
58
 
59
59
  - Check [Python Packaging User Guide](https://packaging.python.org/installing/)
60
- - `pip install v2kit==0.4`
60
+ - `pip install v2kit==0.6`
61
61
 
62
62
  ## Supported Protocols
63
63
 
@@ -4,8 +4,8 @@
4
4
 
5
5
  | Version | Supported |
6
6
  | ------------- | ------------------ |
7
- | 0.4 | :white_check_mark: |
8
- | < 0.4 | :x: |
7
+ | 0.6 | :white_check_mark: |
8
+ | < 0.6 | :x: |
9
9
 
10
10
  ## Reporting a Vulnerability
11
11
 
@@ -23,7 +23,7 @@ def read_description() -> str:
23
23
  setup(
24
24
  name='v2kit',
25
25
  packages=['v2kit', 'v2kit.models'],
26
- version='0.4',
26
+ version='0.6',
27
27
  description='V2Kit: A Lightweight Toolkit for V2Ray Config Manipulation',
28
28
  long_description=read_description(),
29
29
  long_description_content_type='text/markdown',
@@ -31,14 +31,14 @@ setup(
31
31
  author='Sepand Haghighi',
32
32
  author_email='me@sepand.tech',
33
33
  url='https://github.com/sepandhaghighi/v2kit',
34
- download_url='https://github.com/sepandhaghighi/v2kit/tarball/v0.4',
34
+ download_url='https://github.com/sepandhaghighi/v2kit/tarball/v0.6',
35
35
  keywords='v2ray v2ray-config v2ray-tools vmess vless trojan shadowsocks proxy subscription network',
36
36
  project_urls={
37
37
  'Source': 'https://github.com/sepandhaghighi/v2kit'},
38
38
  install_requires=[],
39
39
  python_requires='>=3.8',
40
40
  classifiers=[
41
- 'Development Status :: 3 - Alpha',
41
+ 'Development Status :: 4 - Beta',
42
42
  'Natural Language :: English',
43
43
  'License :: OSI Approved :: MIT License',
44
44
  'Operating System :: OS Independent',
@@ -143,7 +143,7 @@ def test_vless_invalid_uuid():
143
143
  )
144
144
 
145
145
 
146
- def test_vless_invalid_port():
146
+ def test_vless_invalid_port1():
147
147
  with pytest.raises(V2kitValidationError):
148
148
  VLESSConfig(
149
149
  uuid=VALID_UUID,
@@ -152,6 +152,15 @@ def test_vless_invalid_port():
152
152
  )
153
153
 
154
154
 
155
+ def test_vless_invalid_port2():
156
+ with pytest.raises(V2kitValidationError):
157
+ VLESSConfig(
158
+ uuid=VALID_UUID,
159
+ address="example.com",
160
+ port=True,
161
+ )
162
+
163
+
155
164
  def test_vless_empty_address():
156
165
  with pytest.raises(V2kitValidationError):
157
166
  VLESSConfig(
@@ -161,7 +170,7 @@ def test_vless_empty_address():
161
170
  )
162
171
 
163
172
 
164
- def test_vmess_invalid_alter_id():
173
+ def test_vmess_invalid_alter_id1():
165
174
  with pytest.raises(V2kitValidationError):
166
175
  VMESSConfig(
167
176
  uuid=VALID_UUID,
@@ -171,6 +180,16 @@ def test_vmess_invalid_alter_id():
171
180
  )
172
181
 
173
182
 
183
+ def test_vmess_invalid_alter_id2():
184
+ with pytest.raises(V2kitValidationError):
185
+ VMESSConfig(
186
+ uuid=VALID_UUID,
187
+ address="example.com",
188
+ port=443,
189
+ alter_id=True,
190
+ )
191
+
192
+
174
193
  def test_trojan_empty_password():
175
194
  with pytest.raises(V2kitValidationError):
176
195
  TrojanConfig(
@@ -190,7 +209,7 @@ def test_shadowsocks_empty_encryption():
190
209
  )
191
210
 
192
211
 
193
- def test_socks_invalid_port():
212
+ def test_socks_invalid_port1():
194
213
  with pytest.raises(V2kitValidationError):
195
214
  SocksConfig(
196
215
  address="example.com",
@@ -198,6 +217,14 @@ def test_socks_invalid_port():
198
217
  )
199
218
 
200
219
 
220
+ def test_socks_invalid_port2():
221
+ with pytest.raises(V2kitValidationError):
222
+ SocksConfig(
223
+ address="example.com",
224
+ port=False,
225
+ )
226
+
227
+
201
228
  def test_socks_empty_address():
202
229
  with pytest.raises(V2kitValidationError):
203
230
  SocksConfig(
@@ -135,7 +135,7 @@ def test_encode_subscription():
135
135
  assert len(result) > 0
136
136
 
137
137
 
138
- def test_decode_subscription():
138
+ def test_decode_subscription1():
139
139
  configs = [
140
140
  VMESS_CONFIG,
141
141
  VLESS_CONFIG,
@@ -148,6 +148,14 @@ def test_decode_subscription():
148
148
  assert result == configs
149
149
 
150
150
 
151
+ def test_decode_subscription2():
152
+ subscription = "dmxlc3M6Ly8xYzRiNGJjYS1lM2ZmLTRjYTgtYTA2Mi02ZjM5OWFkM2NmNDVAZXhhbXBsZS5jb206NDQzP3NlY3VyaXR5PXRscyN2bGVzcy10ZXN0Cgp0cm9qYW46Ly9wYXNzd29yZEBleGFtcGxlLmNvbTo0NDM/c2VjdXJpdHk9dGxzI3Ryb2phbi10ZXN0CgpzczovL1lXVnpMVEkxTmkxblkyMDZjR0Z6YzNkdmNtUT1AZXhhbXBsZS5jb206ODM4OCNzcy10ZXN0Cg=="
153
+
154
+ result = decode_subscription(subscription)
155
+
156
+ assert len(result) == 3
157
+
158
+
151
159
  def test_encode_decode_subscription_roundtrip():
152
160
  configs = [
153
161
  VMESS_CONFIG,
@@ -79,6 +79,59 @@ def test_update_extra():
79
79
  assert config.extra["foo"] == "bar"
80
80
 
81
81
 
82
+ def test_clear_extra():
83
+ config = HttpConfig(
84
+ address="example.com",
85
+ port=1080,
86
+ extra={"foo": "bar"},
87
+ )
88
+
89
+ config.clear_extra()
90
+
91
+ assert config.extra == {}
92
+
93
+
94
+ def test_set_extra_item():
95
+ config = HttpConfig(
96
+ address="example.com",
97
+ port=1080,
98
+ )
99
+
100
+ config.set_extra_item(
101
+ "foo",
102
+ "bar",
103
+ )
104
+
105
+ assert config.extra["foo"] == "bar"
106
+
107
+
108
+ def test_get_extra_item():
109
+ config = HttpConfig(
110
+ address="example.com",
111
+ port=1080,
112
+ extra={"foo": "bar"},
113
+ )
114
+
115
+ assert config.get_extra_item("foo") == "bar"
116
+ assert config.get_extra_item("missing") is None
117
+ assert config.get_extra_item(
118
+ "missing",
119
+ "default",
120
+ ) == "default"
121
+
122
+
123
+ def test_remove_extra_item():
124
+ config = HttpConfig(
125
+ address="example.com",
126
+ port=1080,
127
+ extra={"foo": "bar"},
128
+ )
129
+
130
+ config.remove_extra_item("foo")
131
+
132
+ assert config.extra == {}
133
+
134
+
82
135
  def test_method_chaining():
83
136
  config = HttpConfig(
84
137
  address="example.com",
@@ -93,12 +146,18 @@ def test_method_chaining():
93
146
  "user"
94
147
  ).update_password(
95
148
  "password"
96
- )
149
+ ).set_extra_item(
150
+ "foo",
151
+ "bar",
152
+ ).remove_extra_item(
153
+ "foo"
154
+ ).clear_extra()
97
155
 
98
156
  assert config.address == "example.org"
99
157
  assert config.port == 8080
100
158
  assert config.username == "user"
101
159
  assert config.password == "password"
160
+ assert config.extra == {}
102
161
 
103
162
 
104
163
  @pytest.mark.parametrize(
@@ -137,6 +196,21 @@ def test_to_uri_roundtrip():
137
196
  assert parsed == config
138
197
 
139
198
 
199
+ def test_encoded_label():
200
+ config = HttpConfig(
201
+ address="example.com",
202
+ port=1080,
203
+ username="user",
204
+ password="password",
205
+ label="test 1 2",
206
+ )
207
+
208
+ parsed = parse(config.to_uri())
209
+ assert parsed == config
210
+ assert config.label == "test 1 2"
211
+ assert config.encoded_label == "test%201%202"
212
+
213
+
140
214
  def test_equality():
141
215
  config1 = HttpConfig(
142
216
  address="example.com",
@@ -67,12 +67,18 @@ def test_method_chaining():
67
67
  "example.org"
68
68
  ).update_port(
69
69
  443
70
- )
70
+ ).set_extra_item(
71
+ "plugin",
72
+ "v2ray-plugin",
73
+ ).remove_extra_item(
74
+ "plugin"
75
+ ).clear_extra()
71
76
 
72
77
  assert config.encryption == "chacha20-ietf-poly1305"
73
78
  assert config.password == "secret"
74
79
  assert config.address == "example.org"
75
80
  assert config.port == 443
81
+ assert config.extra == {}
76
82
 
77
83
 
78
84
  @pytest.mark.parametrize(
@@ -113,6 +119,21 @@ def test_to_uri_roundtrip():
113
119
  assert parsed == config
114
120
 
115
121
 
122
+ def test_encoded_label():
123
+ config = ShadowsocksConfig(
124
+ encryption="aes-256-gcm",
125
+ password="password",
126
+ address="example.com",
127
+ port=8388,
128
+ label="test 1 2",
129
+ )
130
+
131
+ parsed = parse(config.to_uri())
132
+ assert parsed == config
133
+ assert config.label == "test 1 2"
134
+ assert config.encoded_label == "test%201%202"
135
+
136
+
116
137
  def test_equality():
117
138
  config1 = ShadowsocksConfig(
118
139
  encryption="aes-256-gcm",
@@ -150,7 +171,7 @@ def test_repr():
150
171
 
151
172
  assert repr(config) == (
152
173
  "ShadowsocksConfig(protocol=<Protocol.SHADOWSOCKS: "
153
- "'ss'>, label=None)"
174
+ "'shadowsocks'>, label=None)"
154
175
  )
155
176
 
156
177
 
@@ -169,6 +190,71 @@ def test_update_extra():
169
190
  assert config.extra["plugin"] == "v2ray-plugin"
170
191
 
171
192
 
193
+ def test_clear_extra():
194
+ config = ShadowsocksConfig(
195
+ encryption="aes-256-gcm",
196
+ password="password",
197
+ address="example.com",
198
+ port=8388,
199
+ extra={"plugin": "v2ray-plugin"},
200
+ )
201
+
202
+ config.clear_extra()
203
+
204
+ assert config.extra == {}
205
+
206
+
207
+ def test_set_extra_item():
208
+ config = ShadowsocksConfig(
209
+ encryption="aes-256-gcm",
210
+ password="password",
211
+ address="example.com",
212
+ port=8388,
213
+ )
214
+
215
+ config.set_extra_item(
216
+ "plugin",
217
+ "v2ray-plugin",
218
+ )
219
+
220
+ assert config.extra["plugin"] == "v2ray-plugin"
221
+
222
+
223
+ def test_get_extra_item():
224
+ config = ShadowsocksConfig(
225
+ encryption="aes-256-gcm",
226
+ password="password",
227
+ address="example.com",
228
+ port=8388,
229
+ extra={"plugin": "v2ray-plugin"},
230
+ )
231
+
232
+ assert config.get_extra_item(
233
+ "plugin"
234
+ ) == "v2ray-plugin"
235
+ assert config.get_extra_item("missing") is None
236
+ assert config.get_extra_item(
237
+ "missing",
238
+ "default",
239
+ ) == "default"
240
+
241
+
242
+ def test_remove_extra_item():
243
+ config = ShadowsocksConfig(
244
+ encryption="aes-256-gcm",
245
+ password="password",
246
+ address="example.com",
247
+ port=8388,
248
+ extra={"plugin": "v2ray-plugin"},
249
+ )
250
+
251
+ config.remove_extra_item(
252
+ "plugin"
253
+ )
254
+
255
+ assert config.extra == {}
256
+
257
+
172
258
  def test_update_methods():
173
259
  config = ShadowsocksConfig(
174
260
  encryption="aes-256-gcm",
@@ -61,12 +61,18 @@ def test_method_chaining():
61
61
  "user"
62
62
  ).update_password(
63
63
  "password"
64
- )
64
+ ).set_extra_item(
65
+ "version",
66
+ "5",
67
+ ).remove_extra_item(
68
+ "version"
69
+ ).clear_extra()
65
70
 
66
71
  assert config.address == "example.org"
67
72
  assert config.port == 2080
68
73
  assert config.username == "user"
69
74
  assert config.password == "password"
75
+ assert config.extra == {}
70
76
 
71
77
 
72
78
  @pytest.mark.parametrize(
@@ -121,6 +127,63 @@ def test_update_extra():
121
127
  assert config.extra["version"] == "5"
122
128
 
123
129
 
130
+ def test_clear_extra():
131
+ config = SocksConfig(
132
+ address="example.com",
133
+ port=1080,
134
+ extra={"version": "5"},
135
+ )
136
+
137
+ config.clear_extra()
138
+
139
+ assert config.extra == {}
140
+
141
+
142
+ def test_set_extra_item():
143
+ config = SocksConfig(
144
+ address="example.com",
145
+ port=1080,
146
+ )
147
+
148
+ config.set_extra_item(
149
+ "version",
150
+ "5",
151
+ )
152
+
153
+ assert config.extra["version"] == "5"
154
+
155
+
156
+ def test_get_extra_item():
157
+ config = SocksConfig(
158
+ address="example.com",
159
+ port=1080,
160
+ extra={"version": "5"},
161
+ )
162
+
163
+ assert config.get_extra_item(
164
+ "version"
165
+ ) == "5"
166
+ assert config.get_extra_item("missing") is None
167
+ assert config.get_extra_item(
168
+ "missing",
169
+ "default",
170
+ ) == "default"
171
+
172
+
173
+ def test_remove_extra_item():
174
+ config = SocksConfig(
175
+ address="example.com",
176
+ port=1080,
177
+ extra={"version": "5"},
178
+ )
179
+
180
+ config.remove_extra_item(
181
+ "version"
182
+ )
183
+
184
+ assert config.extra == {}
185
+
186
+
124
187
  def test_to_uri_roundtrip():
125
188
  config = SocksConfig(
126
189
  address="example.com",
@@ -135,6 +198,21 @@ def test_to_uri_roundtrip():
135
198
  assert parsed == config
136
199
 
137
200
 
201
+ def test_encoded_label():
202
+ config = SocksConfig(
203
+ address="example.com",
204
+ port=1080,
205
+ username="user",
206
+ password="password",
207
+ label="test 1 2",
208
+ )
209
+
210
+ parsed = parse(config.to_uri())
211
+ assert parsed == config
212
+ assert config.label == "test 1 2"
213
+ assert config.encoded_label == "test%201%202"
214
+
215
+
138
216
  def test_equality():
139
217
  config1 = SocksConfig(
140
218
  address="example.com",