twc-cli 2.10.0__py3-none-any.whl → 2.11.0__py3-none-any.whl

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.

Potentially problematic release.


This version of twc-cli might be problematic. Click here for more details.

CHANGELOG.md CHANGED
@@ -2,6 +2,31 @@
2
2
 
3
3
  В этом файле описаны все значимые изменения в Timeweb Cloud CLI. В выпусках мы придерживается правил [семантического версионирования](https://semver.org/lang/ru/).
4
4
 
5
+ # Версия 2.11.0 (2025.04.01)
6
+
7
+ ## Добавлено
8
+
9
+ - Добавлены новые опции к команде `twc balancer create`:
10
+ - для настройки сети: `--network-id`, `--public-ip`, `--no-public-ip`, `--private-ip`;
11
+ - для установки TLS: `--cert-type`, `--cert-domain`, `--cert-data`, `--cert-key`;
12
+ - выбор локации и пресета: `--region`, `--availability-zone`, `--preset-id`;
13
+ - дополнительные: `--desc` (описание балансировщика нагрузки).
14
+ - Добавлена новая команда для просмотра списка доступных пресетов балансировщиков нагрузки — `twc balancer list-presets`.
15
+
16
+ ## Изменено
17
+
18
+ - Опция `--network` команды `twc balancer create` объявлена устаревшей и скрыта. Вместо неё используйте `--network-id`.
19
+
20
+ ## Исправлено
21
+
22
+ - Исправлена ошибка в команде `twc server create` возникавшая при передаче парамера `--project-id`.
23
+
24
+ # Версия 2.10.1 (2025.03.25)
25
+
26
+ ## Исправлено
27
+
28
+ - Исправлена ошибка получения публичного IP при создании кластера СУБД.
29
+
5
30
  # Версия 2.10.0 (2025.03.24)
6
31
 
7
32
  ## Добавлено
twc/__version__.py CHANGED
@@ -12,5 +12,5 @@
12
12
  import sys
13
13
 
14
14
 
15
- __version__ = "2.10.0"
15
+ __version__ = "2.11.0"
16
16
  __pyversion__ = sys.version.replace("\n", "")
twc/api/client.py CHANGED
@@ -78,6 +78,7 @@ class TimewebCloud(TimewebCloudBase):
78
78
  network: Optional[dict] = None,
79
79
  availability_zone: Optional[ServiceAvailabilityZone] = None,
80
80
  is_root_password_required: Optional[bool] = None,
81
+ project_id: Optional[int] = None,
81
82
  ):
82
83
  """Create new Cloud Server. Note:
83
84
 
@@ -120,6 +121,7 @@ class TimewebCloud(TimewebCloudBase):
120
121
  if is_root_password_required is not None
121
122
  else {}
122
123
  ),
124
+ **({"project_id": project_id} if project_id else {}),
123
125
  }
124
126
 
125
127
  return self._request("POST", f"{self.api_url}/servers", json=payload)
@@ -1157,6 +1159,9 @@ class TimewebCloud(TimewebCloudBase):
1157
1159
  force_https: bool = False,
1158
1160
  backend_keepalive: bool = False,
1159
1161
  network: Optional[dict] = None,
1162
+ comment: Optional[str] = None,
1163
+ project_id: Optional[int] = None,
1164
+ certificates: Optional[dict] = None,
1160
1165
  ):
1161
1166
  """Create load balancer."""
1162
1167
  payload = {
@@ -1175,6 +1180,9 @@ class TimewebCloud(TimewebCloudBase):
1175
1180
  "is_ssl": force_https,
1176
1181
  "is_keepalive": backend_keepalive,
1177
1182
  **({"network": network} if network else {}),
1183
+ **({"comment": comment} if comment else {}),
1184
+ **({"project_id": project_id} if project_id else {}),
1185
+ **({"certificates": certificates} if certificates else {}),
1178
1186
  }
1179
1187
  return self._request("POST", f"{self.api_url}/balancers", json=payload)
1180
1188
 
twc/api/exceptions.py CHANGED
@@ -15,11 +15,13 @@ class ErrResponse:
15
15
  error_code: str = None,
16
16
  message: Union[str, List[str]] = None,
17
17
  response_id: UUID = None,
18
+ **kwargs,
18
19
  ):
19
20
  self.status_code = status_code
20
21
  self.error_code = error_code
21
22
  self.message = message
22
23
  self.response_id = response_id
24
+ self.kwargs = kwargs
23
25
 
24
26
 
25
27
  class TimewebCloudException(HTTPError):
twc/commands/balancer.py CHANGED
@@ -2,9 +2,11 @@
2
2
 
3
3
  import re
4
4
  import sys
5
+ from enum import Enum
5
6
  from typing import Optional, List
6
7
  from pathlib import Path
7
8
  from logging import debug
9
+ from ipaddress import IPv4Address, IPv4Network
8
10
 
9
11
  import typer
10
12
  from requests import Response
@@ -13,20 +15,23 @@ from twc import fmt
13
15
  from twc.typerx import TyperAlias
14
16
  from twc.apiwrap import create_client
15
17
  from twc.api import TimewebCloud
16
- from twc.api.types import LoadBalancerAlgo, LoadBalancerProto
18
+ from twc.vars import REGION_ZONE_MAP
19
+ from twc.api.types import LoadBalancerAlgo, LoadBalancerProto, ServiceRegion
17
20
  from .common import (
18
21
  verbose_option,
19
22
  config_option,
20
23
  profile_option,
21
24
  filter_option,
22
25
  yes_option,
26
+ region_option,
27
+ zone_option,
23
28
  output_format_option,
24
29
  load_from_config_callback,
25
30
  )
26
31
 
27
32
 
28
33
  balancer = TyperAlias(help=__doc__)
29
- balancer_backend = TyperAlias(help="Manage load balancer backends.")
34
+ balancer_backend = TyperAlias(help="Manage load balancer backend servers.")
30
35
  balancer_rule = TyperAlias(help="Manage load balancer rules.")
31
36
  balancer.add_typer(balancer_backend, name="backend", aliases=["backends"])
32
37
  balancer.add_typer(balancer_rule, name="rule", aliases=["rules"])
@@ -144,6 +149,13 @@ def balancer_get(
144
149
  # ------------------------------------------------------------- #
145
150
 
146
151
 
152
+ class CertType(str, Enum):
153
+ """..."""
154
+
155
+ CUSTOM = "custom"
156
+ LETS_ENCRYPT = "lets_encrypt"
157
+
158
+
147
159
  @balancer.command("create")
148
160
  def balancer_create(
149
161
  verbose: Optional[bool] = verbose_option,
@@ -151,11 +163,17 @@ def balancer_create(
151
163
  profile: Optional[str] = profile_option,
152
164
  output_format: Optional[str] = output_format_option,
153
165
  name: str = typer.Option(..., help="Load balancer display name."),
166
+ desc: Optional[str] = typer.Option(
167
+ None, help="Load balancer description."
168
+ ),
169
+ preset_id: Optional[int] = typer.Option(
170
+ None, help="Load balancer preset ID."
171
+ ),
154
172
  replicas: int = typer.Option(
155
173
  1,
156
174
  min=1,
157
175
  max=2,
158
- help="Load balancer replica count.",
176
+ help="Load balancer replica count. Ignored if --preset-id set.",
159
177
  ),
160
178
  algo: LoadBalancerAlgo = typer.Option(
161
179
  LoadBalancerAlgo.ROUND_ROBIN.value,
@@ -187,28 +205,44 @@ def balancer_create(
187
205
  callback=load_from_config_callback,
188
206
  help="Add load balancer to specific project.",
189
207
  ),
190
- network: Optional[str] = typer.Option(None, help="Private network ID."),
208
+ network: Optional[str] = typer.Option(
209
+ None, hidden=True, help="Private network ID."
210
+ ),
211
+ network_id: Optional[str] = typer.Option(None, help="Private network ID."),
212
+ private_ip: Optional[str] = typer.Option(
213
+ None, help="Private IPv4 address."
214
+ ),
215
+ public_ip: Optional[str] = typer.Option(
216
+ None, help="Public IPv4 address. New address by default."
217
+ ),
218
+ no_public_ip: Optional[bool] = typer.Option(
219
+ False, "--no-public-ip", help="Do not add public IPv4 address."
220
+ ),
221
+ region: Optional[str] = region_option,
222
+ availability_zone: Optional[str] = zone_option,
223
+ cert_type: Optional[CertType] = typer.Option(
224
+ None,
225
+ help="SSL certificate type. Falls to 'custom' "
226
+ "if --cert-data and --cert-key set.",
227
+ ),
228
+ cert_domain: Optional[str] = typer.Option(
229
+ None,
230
+ help="Domain name for which the certificate was issued. "
231
+ "Note: domain name A-record will set to load balancer's public IP.",
232
+ ),
233
+ cert_data: Optional[typer.FileText] = typer.Option(
234
+ None, help="Fullchain certificate file."
235
+ ),
236
+ cert_key: Optional[typer.FileText] = typer.Option(
237
+ None, help="Certificate key file."
238
+ ),
191
239
  ):
192
240
  """Create load balancer."""
193
241
  client = create_client(config, profile)
194
242
 
195
- preset_id = None
196
- for preset in client.get_load_balancer_presets().json()[
197
- "balancers_presets"
198
- ]:
199
- if preset["replica_count"] == replicas:
200
- preset_id = preset["id"]
201
- if not preset_id:
202
- sys.exit(f"Error: Cannot set {replicas} load balancer replicas.")
203
-
204
- if project_id:
205
- if not project_id in [
206
- prj["id"] for prj in client.get_projects().json()["projects"]
207
- ]:
208
- sys.exit(f"Wrong project ID: Project '{project_id}' not found.")
209
-
210
243
  payload = {
211
244
  "name": name,
245
+ "comment": desc,
212
246
  "preset_id": preset_id,
213
247
  "algo": algo,
214
248
  "proto": proto,
@@ -222,21 +256,108 @@ def balancer_create(
222
256
  "proxy_protocol": proxy_protocol,
223
257
  "force_https": force_https,
224
258
  "backend_keepalive": backend_keepalive,
259
+ "network": {},
260
+ "project_id": project_id,
261
+ "certificates": {},
225
262
  }
226
263
 
264
+ if cert_type == CertType.CUSTOM:
265
+ if not cert_data or not cert_key:
266
+ sys.exit(
267
+ "Error: --cert-data and --cert-key is required if --cert-type is 'custom'"
268
+ )
269
+ elif cert_type == CertType.LETS_ENCRYPT:
270
+ if cert_data or cert_key:
271
+ sys.exit(
272
+ "Error: --cert-data and --cert-key is not allowed with --cert-type 'lets_encrypt'"
273
+ )
274
+ if not cert_type:
275
+ if cert_data and not cert_key:
276
+ sys.exit("Error: --cert-key is required.")
277
+ if cert_key and not cert_data:
278
+ sys.exit("Error: --cert-data is required.")
279
+ if cert_data and cert_key:
280
+ cert_type = CertType.CUSTOM
281
+ if cert_type and not cert_domain:
282
+ sys.exit(
283
+ "Error: --cert-domain is required if --cert-type and/or --cert-data, --cert-key is set."
284
+ )
285
+ if cert_type:
286
+ payload["certificates"] = {
287
+ "type": cert_type.value,
288
+ "fqdn": cert_domain,
289
+ **({"cert_data": cert_data.read()} if cert_data else {}),
290
+ **({"key_data": cert_key.read()} if cert_key else {}),
291
+ }
292
+
293
+ if not preset_id:
294
+ for preset in client.get_load_balancer_presets().json()[
295
+ "balancers_presets"
296
+ ]:
297
+ if (
298
+ preset["replica_count"] == replicas
299
+ and preset["location"] == region
300
+ ):
301
+ preset_id = preset["id"]
302
+ if not preset_id:
303
+ sys.exit(f"Error: Cannot set {replicas} load balancer replicas.")
304
+
227
305
  if network:
228
- payload["network"] = {"id": network}
306
+ print(
307
+ "Option --network is deprecated and will be removed soon, "
308
+ "use --network-id instead",
309
+ file=sys.stderr,
310
+ )
311
+ network_id = network
312
+
313
+ if network_id:
314
+ payload["network"]["id"] = network_id
315
+ if private_ip:
316
+ net = IPv4Network(
317
+ client.get_vpc(network_id).json()["vpc"]["subnet_v4"]
318
+ )
319
+ if IPv4Address(private_ip) >= net.network_address + 4:
320
+ payload["network"]["ip"] = private_ip
321
+ else:
322
+ # First 3 addresses is reserved by Timeweb Cloud for gateway and future use.
323
+ sys.exit(
324
+ f"Error: Private address '{private_ip}' is not allowed. "
325
+ "IP must be at least the fourth in order in the network."
326
+ )
327
+ if public_ip:
328
+ try:
329
+ _ = IPv4Address(public_ip)
330
+ payload["network"]["floating_ip"] = public_ip
331
+ except ValueError:
332
+ sys.exit(f"Error: '{public_ip}' is not valid IPv4 address.")
333
+ else:
334
+ # Get new public IPv4 address.
335
+ if no_public_ip is False:
336
+ zone = None
337
+ if not preset_id and not availability_zone and not region:
338
+ sys.exit(
339
+ "Error: Unable to get IPv4 address, at least one of "
340
+ "[--preset-id, --region, --availability-zone] "
341
+ "must be set to determine correct location."
342
+ )
343
+ if region:
344
+ zone = REGION_ZONE_MAP[region]
345
+ if preset_id and not availability_zone:
346
+ for preset in client.get_database_presets().json()[
347
+ "databases_presets"
348
+ ]:
349
+ if preset["id"] == preset_id:
350
+ zone = REGION_ZONE_MAP[preset["location"]]
351
+ if availability_zone:
352
+ zone = availability_zone
353
+ ip = client.create_floating_ip(availability_zone=zone).json()[
354
+ "ip"
355
+ ]["ip"]
356
+ payload["network"]["floating_ip"] = ip
229
357
 
230
358
  # Create LB
231
359
  response = client.create_load_balancer(**payload)
232
360
 
233
- # Add created LB to project if set
234
- if project_id:
235
- client.add_balancer_to_project(
236
- response.json()["balancer"]["id"],
237
- project_id,
238
- )
239
-
240
361
  fmt.printer(
241
362
  response,
242
363
  output_format=output_format,
@@ -384,6 +505,64 @@ def blancer_backend_list(
384
505
  )
385
506
 
386
507
 
508
+ # ------------------------------------------------------------- #
509
+ # $ twc balancer list-presets #
510
+ # ------------------------------------------------------------- #
511
+
512
+
513
+ def _print_presets(response: Response, filters: Optional[str] = None):
514
+ presets = response.json()["balancers_presets"]
515
+ if filters:
516
+ presets = fmt.filter_list(presets, filters)
517
+ table = fmt.Table()
518
+ table.header(
519
+ [
520
+ "ID",
521
+ "REGION",
522
+ "PRICE",
523
+ "REPLICAS",
524
+ "BANDW",
525
+ "RPS",
526
+ ]
527
+ )
528
+ for preset in presets:
529
+ table.row(
530
+ [
531
+ preset["id"],
532
+ preset["location"],
533
+ preset["price"],
534
+ preset["replica_count"],
535
+ preset["bandwidth"],
536
+ preset["request_per_second"],
537
+ ]
538
+ )
539
+ table.print()
540
+
541
+
542
+ @balancer.command("list-presets", "lp")
543
+ def balancer_list_presets(
544
+ verbose: Optional[bool] = verbose_option,
545
+ config: Optional[Path] = config_option,
546
+ profile: Optional[str] = profile_option,
547
+ output_format: Optional[str] = output_format_option,
548
+ filters: Optional[str] = filter_option,
549
+ region: Optional[ServiceRegion] = typer.Option(
550
+ None, help="Use region (location)."
551
+ ),
552
+ ):
553
+ """List configuration presets."""
554
+ if region:
555
+ filters = f"{filters},location:{region}"
556
+ client = create_client(config, profile)
557
+ response = client.get_load_balancer_presets()
558
+ fmt.printer(
559
+ response,
560
+ output_format=output_format,
561
+ filters=filters,
562
+ func=_print_presets,
563
+ )
564
+
565
+
387
566
  # ------------------------------------------------------------- #
388
567
  # $ twc balancer backend add #
389
568
  # ------------------------------------------------------------- #
@@ -392,7 +571,7 @@ def blancer_backend_list(
392
571
  @balancer_backend.command("add")
393
572
  def blancer_backend_add(
394
573
  balancer_id: int = typer.Argument(...),
395
- backends: List[str] = typer.Argument(..., metavar="BACKEND..."),
574
+ backends: List[str] = typer.Argument(..., metavar="BACKEND_IP..."),
396
575
  verbose: Optional[bool] = verbose_option,
397
576
  config: Optional[Path] = config_option,
398
577
  profile: Optional[str] = profile_option,
twc/commands/database.py CHANGED
@@ -15,6 +15,7 @@ from twc import fmt
15
15
  from twc.typerx import TyperAlias
16
16
  from twc.api import ServiceRegion, MySQLAuthPlugin, BackupInterval
17
17
  from twc.apiwrap import create_client
18
+ from twc.vars import REGION_ZONE_MAP
18
19
  from twc.utils import merge_dicts
19
20
  from .common import (
20
21
  verbose_option,
@@ -24,6 +25,7 @@ from .common import (
24
25
  yes_option,
25
26
  output_format_option,
26
27
  load_from_config_callback,
28
+ zone_option,
27
29
  )
28
30
 
29
31
 
@@ -277,6 +279,7 @@ def database_create(
277
279
  config: Optional[Path] = config_option,
278
280
  profile: Optional[str] = profile_option,
279
281
  output_format: Optional[str] = output_format_option,
282
+ availability_zone: Optional[str] = zone_option,
280
283
  preset_id: int = typer.Option(..., help="Database configuration preset."),
281
284
  dbms: str = typer.Option(
282
285
  ...,
@@ -408,10 +411,21 @@ def database_create(
408
411
  except ValueError:
409
412
  sys.exit(f"Error: '{public_ip}' is not valid IPv4 address.")
410
413
  else:
411
- # New public IPv4 address will be automatically requested with
412
- # correct availability zone. This is an official dirty hack.
414
+ # Get new public IPv4 address.
413
415
  if no_public_ip is False:
414
- payload["network"]["floating_ip"] = "create_ip"
416
+ zone = None
417
+ if preset_id and not availability_zone:
418
+ for preset in client.get_database_presets().json()[
419
+ "databases_presets"
420
+ ]:
421
+ if preset["id"] == preset_id:
422
+ zone = REGION_ZONE_MAP[preset["location"]]
423
+ if availability_zone:
424
+ zone = availability_zone
425
+ ip = client.create_floating_ip(availability_zone=zone).json()[
426
+ "ip"
427
+ ]["ip"]
428
+ payload["network"]["floating_ip"] = ip
415
429
 
416
430
  if login:
417
431
  print(
twc/vars.py CHANGED
@@ -20,3 +20,13 @@ ZONES_WITH_LAN = [
20
20
  "gdn-1",
21
21
  "fra-1",
22
22
  ]
23
+ # The default availability zones per regions.
24
+ REGION_ZONE_MAP = {
25
+ "ru-1": "spb-3",
26
+ "ru-2": "nsk-1",
27
+ "ru-3": "msk-1",
28
+ "kz-1": "ala-1",
29
+ "pl-1": "gdn-1",
30
+ "nl-1": "ams-1",
31
+ "de-1": "fra-1",
32
+ }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: twc-cli
3
- Version: 2.10.0
3
+ Version: 2.11.0
4
4
  Summary: Timeweb Cloud Command Line Interface.
5
5
  Home-page: https://github.com/timeweb-cloud/twc
6
6
  License: MIT
@@ -1,20 +1,20 @@
1
- CHANGELOG.md,sha256=9i0MuhpW4DSXw848ZGwm7AdA-XA9rYyOigBRT2T76cU,29632
1
+ CHANGELOG.md,sha256=13dFSq5_w3jOkyqJWX39HZDmScz6IZiBKrDkmA0wMQQ,30968
2
2
  COPYING,sha256=fpJLxZS_kHr_659xkpmqEtqHeZp6lQR9CmKCwnYbsmE,1089
3
3
  twc/__init__.py,sha256=NwPAMNw3NuHdWGQvWS9_lromVF6VM194oVOipojfJns,113
4
4
  twc/__main__.py,sha256=ADHceaQUzgLmvhYHvb5O8urdJWj5IcEHLpTQkSExiD8,2468
5
- twc/__version__.py,sha256=hlCsp4lDiHyiPjydCpKPl4urI6KoC8-yx7qcaoAret8,443
5
+ twc/__version__.py,sha256=mwk8unQ99PJ4YQ7EDXxikgVBdMFiYEKCiMYG2c5C-ZY,443
6
6
  twc/api/__init__.py,sha256=SXew0Fe51M7nRBNQaaLRH4NjnRHkQUn7J26OCkQsftA,128
7
7
  twc/api/base.py,sha256=QRefnIgmlbz8n37GLBKeAK1AtzkcNo1IFjZgHDDECJ4,7912
8
- twc/api/client.py,sha256=T74KLeKHyAxzKHkpdNF-VOkfiAwnu7us61xzPosV5_o,64366
9
- twc/api/exceptions.py,sha256=UzK3pKRffcXlhnkPy6MDjP_DygVoV17DuZ_mdNbOzts,2369
8
+ twc/api/client.py,sha256=zEpBs_wNaUiS7jMiDikrkuIZE_SggCtdXhihXFt0R7o,64795
9
+ twc/api/exceptions.py,sha256=6nMU20f-Xe7EbH2jpfmSGMW69Rwfhh_ph9ygz37G0XY,2416
10
10
  twc/api/types.py,sha256=uagnD3TPpoJFYUFK6HfHQPlEXs2GLxuJdjhNIbraXwM,5374
11
11
  twc/apiwrap.py,sha256=hKrg_o6rLfY32SEnWMc1BSXHnSAh7TGar1JQ90YnG5M,2970
12
12
  twc/commands/__init__.py,sha256=a-6fHQQwOj--Z7uBZGZL3z1rvJiOGUMQMRET1UknIYo,430
13
13
  twc/commands/account.py,sha256=6q9ri02oFbUUZuqNVXO-uHOX45B4ELJlPjyfVaEL5Qw,5960
14
- twc/commands/balancer.py,sha256=QAouc74ZT5go11gB1vjjfYtd1luTmWrfpACPwokZ5sU,20278
14
+ twc/commands/balancer.py,sha256=ux1SGsUPNPCsonbE8p1ABymE7pA2abX8Omm2VhZijy4,26585
15
15
  twc/commands/common.py,sha256=Wph8cVogUNNvc456SQrASb7mv7G88I8ETwHgISVjLQQ,8282
16
16
  twc/commands/config.py,sha256=xHNEZVmM60c9dApLfNsj78sXZk6VsFwPdVIHO9r8xks,8802
17
- twc/commands/database.py,sha256=dX0z8LLpVTfCM7QNe0tU1DH7IWIwaJZBdSqHs6hWzNQ,31446
17
+ twc/commands/database.py,sha256=NOi5b-DGYgbbN7bPrsJt0wKTJBzrfKUPwltVx8YGsgU,31953
18
18
  twc/commands/domain.py,sha256=BIg5k0TDQ-iWnhjuAHaWlZBB0bfaZgqZ2EWZGk3BICA,17154
19
19
  twc/commands/firewall.py,sha256=KNolqbi2rsppOZwbs_j3yoZQt-0wKbj1JPGiZdfGxDE,27439
20
20
  twc/commands/floating_ip.py,sha256=G9nD5BbHCZcuytbzeneDJWQDhd8c8WRtq9pAfwI9m7E,8747
@@ -28,9 +28,9 @@ twc/commands/vpc.py,sha256=SAht6UD17mU0d_AZY6W34VEYs7CqUsS2iDakPFxAFQU,8876
28
28
  twc/fmt.py,sha256=nbuYZ8nVabYDwCmZqnL3-c6Tmri4B-R_sTCkG6sdfeI,7171
29
29
  twc/typerx.py,sha256=AZ6BgTQvlrZYfKVYd9YqRNQnAR2XuyqImz4rf6di6f4,6737
30
30
  twc/utils.py,sha256=uWizyUC4dHLwtk50q4Sub3zOvnVESfHKBbXYwk5t71w,651
31
- twc/vars.py,sha256=XRtd77WWy2Ym2Yaodx84CKqKu2272O-Bj_fMFCjzuwg,607
32
- twc_cli-2.10.0.dist-info/COPYING,sha256=fpJLxZS_kHr_659xkpmqEtqHeZp6lQR9CmKCwnYbsmE,1089
33
- twc_cli-2.10.0.dist-info/METADATA,sha256=ZTVW8woDo4IdmAsKC-Okd4tHJ6CKKNgBQ7pnKw5Yc98,2653
34
- twc_cli-2.10.0.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
35
- twc_cli-2.10.0.dist-info/entry_points.txt,sha256=tmTaVRhm8BkNrXC_9XJMum7O9wFVOvkXcBetxmahWvE,40
36
- twc_cli-2.10.0.dist-info/RECORD,,
31
+ twc/vars.py,sha256=AHZEwtQ_BQbnP0n7RXJ-qhqeKPQ_FPc8SDgg6osR5uU,822
32
+ twc_cli-2.11.0.dist-info/COPYING,sha256=fpJLxZS_kHr_659xkpmqEtqHeZp6lQR9CmKCwnYbsmE,1089
33
+ twc_cli-2.11.0.dist-info/METADATA,sha256=2PapxhJ2AA5vCK_xy76_2dC2HqW3dphy49ssYxh-6e0,2653
34
+ twc_cli-2.11.0.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
35
+ twc_cli-2.11.0.dist-info/entry_points.txt,sha256=tmTaVRhm8BkNrXC_9XJMum7O9wFVOvkXcBetxmahWvE,40
36
+ twc_cli-2.11.0.dist-info/RECORD,,