remotivelabs-cli 0.0.32__tar.gz → 0.0.34__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 (50) hide show
  1. {remotivelabs_cli-0.0.32 → remotivelabs_cli-0.0.34}/PKG-INFO +3 -2
  2. {remotivelabs_cli-0.0.32 → remotivelabs_cli-0.0.34}/cli/broker/lib/broker.py +30 -22
  3. {remotivelabs_cli-0.0.32 → remotivelabs_cli-0.0.34}/cli/broker/signals.py +9 -11
  4. remotivelabs_cli-0.0.34/cli/cloud/auth/__init__.py +3 -0
  5. remotivelabs_cli-0.0.34/cli/cloud/auth/cmd.py +46 -0
  6. remotivelabs_cli-0.0.32/cli/cloud/auth.py → remotivelabs_cli-0.0.34/cli/cloud/auth/login.py +3 -49
  7. {remotivelabs_cli-0.0.32 → remotivelabs_cli-0.0.34}/cli/cloud/cloud_cli.py +1 -2
  8. {remotivelabs_cli-0.0.32 → remotivelabs_cli-0.0.34}/cli/cloud/storage/cmd.py +4 -5
  9. remotivelabs_cli-0.0.34/cli/cloud/storage/uri.py +113 -0
  10. remotivelabs_cli-0.0.34/cli/tools/can/__init__.py +0 -0
  11. {remotivelabs_cli-0.0.32 → remotivelabs_cli-0.0.34}/pyproject.toml +8 -10
  12. remotivelabs_cli-0.0.32/cli/cloud/storage/uri.py +0 -59
  13. {remotivelabs_cli-0.0.32 → remotivelabs_cli-0.0.34}/LICENSE +0 -0
  14. {remotivelabs_cli-0.0.32 → remotivelabs_cli-0.0.34}/README.md +0 -0
  15. {remotivelabs_cli-0.0.32 → remotivelabs_cli-0.0.34}/cli/__init__.py +0 -0
  16. {remotivelabs_cli-0.0.32 → remotivelabs_cli-0.0.34}/cli/broker/brokers.py +0 -0
  17. {remotivelabs_cli-0.0.32 → remotivelabs_cli-0.0.34}/cli/broker/export.py +0 -0
  18. {remotivelabs_cli-0.0.32 → remotivelabs_cli-0.0.34}/cli/broker/files.py +0 -0
  19. {remotivelabs_cli-0.0.32 → remotivelabs_cli-0.0.34}/cli/broker/lib/__about__.py +0 -0
  20. {remotivelabs_cli-0.0.32 → remotivelabs_cli-0.0.34}/cli/broker/license_flows.py +0 -0
  21. {remotivelabs_cli-0.0.32 → remotivelabs_cli-0.0.34}/cli/broker/licenses.py +0 -0
  22. {remotivelabs_cli-0.0.32 → remotivelabs_cli-0.0.34}/cli/broker/playback.py +0 -0
  23. {remotivelabs_cli-0.0.32 → remotivelabs_cli-0.0.34}/cli/broker/record.py +0 -0
  24. {remotivelabs_cli-0.0.32 → remotivelabs_cli-0.0.34}/cli/broker/scripting.py +0 -0
  25. {remotivelabs_cli-0.0.32 → remotivelabs_cli-0.0.34}/cli/cloud/__init__.py +0 -0
  26. {remotivelabs_cli-0.0.32 → remotivelabs_cli-0.0.34}/cli/cloud/auth_tokens.py +0 -0
  27. {remotivelabs_cli-0.0.32 → remotivelabs_cli-0.0.34}/cli/cloud/brokers.py +0 -0
  28. {remotivelabs_cli-0.0.32 → remotivelabs_cli-0.0.34}/cli/cloud/configs.py +0 -0
  29. {remotivelabs_cli-0.0.32 → remotivelabs_cli-0.0.34}/cli/cloud/organisations.py +0 -0
  30. {remotivelabs_cli-0.0.32 → remotivelabs_cli-0.0.34}/cli/cloud/projects.py +0 -0
  31. {remotivelabs_cli-0.0.32 → remotivelabs_cli-0.0.34}/cli/cloud/recordings.py +0 -0
  32. {remotivelabs_cli-0.0.32 → remotivelabs_cli-0.0.34}/cli/cloud/recordings_playback.py +0 -0
  33. {remotivelabs_cli-0.0.32 → remotivelabs_cli-0.0.34}/cli/cloud/rest_helper.py +0 -0
  34. {remotivelabs_cli-0.0.32 → remotivelabs_cli-0.0.34}/cli/cloud/resumable_upload.py +0 -0
  35. {remotivelabs_cli-0.0.32 → remotivelabs_cli-0.0.34}/cli/cloud/sample_recordings.py +0 -0
  36. {remotivelabs_cli-0.0.32 → remotivelabs_cli-0.0.34}/cli/cloud/service_account_tokens.py +0 -0
  37. {remotivelabs_cli-0.0.32 → remotivelabs_cli-0.0.34}/cli/cloud/service_accounts.py +0 -0
  38. {remotivelabs_cli-0.0.32 → remotivelabs_cli-0.0.34}/cli/cloud/storage/__init__.py +0 -0
  39. {remotivelabs_cli-0.0.32 → remotivelabs_cli-0.0.34}/cli/cloud/storage/copy.py +0 -0
  40. {remotivelabs_cli-0.0.32 → remotivelabs_cli-0.0.34}/cli/cloud/storage/uri_or_path.py +0 -0
  41. {remotivelabs_cli-0.0.32 → remotivelabs_cli-0.0.34}/cli/connect/__init__.py +0 -0
  42. {remotivelabs_cli-0.0.32 → remotivelabs_cli-0.0.34}/cli/connect/connect.py +0 -0
  43. {remotivelabs_cli-0.0.32 → remotivelabs_cli-0.0.34}/cli/connect/protopie/protopie.py +0 -0
  44. {remotivelabs_cli-0.0.32 → remotivelabs_cli-0.0.34}/cli/errors.py +0 -0
  45. {remotivelabs_cli-0.0.32 → remotivelabs_cli-0.0.34}/cli/remotive.py +0 -0
  46. {remotivelabs_cli-0.0.32 → remotivelabs_cli-0.0.34}/cli/settings.py +0 -0
  47. {remotivelabs_cli-0.0.32 → remotivelabs_cli-0.0.34}/cli/tools/__init__.py +0 -0
  48. /remotivelabs_cli-0.0.32/cli/tools/can/__init__.py → /remotivelabs_cli-0.0.34/cli/tools/can/RemotiveLabs.can1.log +0 -0
  49. {remotivelabs_cli-0.0.32 → remotivelabs_cli-0.0.34}/cli/tools/can/can.py +0 -0
  50. {remotivelabs_cli-0.0.32 → remotivelabs_cli-0.0.34}/cli/tools/tools.py +0 -0
@@ -1,15 +1,16 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: remotivelabs-cli
3
- Version: 0.0.32
3
+ Version: 0.0.34
4
4
  Summary: CLI for operating RemotiveCloud and RemotiveBroker
5
5
  Author: Johan Rask
6
6
  Author-email: johan.rask@remotivelabs.com
7
- Requires-Python: >=3.8,<3.12
7
+ Requires-Python: >=3.8
8
8
  Classifier: Programming Language :: Python :: 3
9
9
  Classifier: Programming Language :: Python :: 3.8
10
10
  Classifier: Programming Language :: Python :: 3.9
11
11
  Classifier: Programming Language :: Python :: 3.10
12
12
  Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
13
14
  Requires-Dist: grpc-stubs (>=1.53.0.5)
14
15
  Requires-Dist: mypy-protobuf (>=3.0.0)
15
16
  Requires-Dist: plotext (>=5.2,<6.0)
@@ -18,6 +18,7 @@ import remotivelabs.broker.generated.sync.traffic_api_pb2 as traffic_api
18
18
  import remotivelabs.broker.sync as br
19
19
  import remotivelabs.broker.sync.helper as br_helper
20
20
  import typer
21
+ from google.protobuf.json_format import MessageToDict
21
22
  from rich.console import Console
22
23
 
23
24
  from cli import settings
@@ -360,7 +361,7 @@ class Broker:
360
361
  namespaces.append(network_info.namespace.name)
361
362
  return namespaces
362
363
 
363
- def list_signal_names(self) -> List[Dict[str, Any]]:
364
+ def list_signal_names(self, prefix: Union[str, None], suffix: Union[str, None]) -> List[Dict[str, Any]]:
364
365
  # Lists available signals
365
366
  configuration = self.system_stub.GetConfiguration(br.common_pb2.Empty())
366
367
 
@@ -368,30 +369,37 @@ class Broker:
368
369
  for network_info in configuration.networkInfo:
369
370
  res = self.system_stub.ListSignals(network_info.namespace)
370
371
  for finfo in res.frame:
371
- # f: br.common_pb2.FrameInfo = finfo
372
- receivers = []
373
- for sinfo in finfo.childInfo:
374
- rec = list(map(lambda r: r, sinfo.metaData.receiver))
375
- receivers.extend(rec)
376
- signal_names.append(
377
- {
378
- "signal": sinfo.id.name,
379
- "namespace": network_info.namespace.name,
380
- "receivers": rec,
381
- "min": sinfo.metaData.min,
382
- "max": sinfo.metaData.max,
383
- }
372
+ if (prefix is None or finfo.signalInfo.id.name.startswith(prefix)) and (
373
+ suffix is None or finfo.signalInfo.id.name.endswith(suffix)
374
+ ):
375
+ metadata_dict = MessageToDict(
376
+ finfo.signalInfo.metaData,
377
+ including_default_value_fields=True,
378
+ preserving_proto_field_name=True,
384
379
  )
385
-
386
- signal_names.append(
387
- {
380
+ sig_dict = {
388
381
  "signal": finfo.signalInfo.id.name,
389
382
  "namespace": network_info.namespace.name,
390
- "senders": list(map(lambda s: s, finfo.signalInfo.metaData.sender)),
391
- "receivers": list(set(receivers)),
392
- "cycletime": finfo.signalInfo.metaData.cycleTime,
393
383
  }
394
- )
384
+ signal_names.append({**sig_dict, **metadata_dict})
385
+
386
+ for sinfo in finfo.childInfo:
387
+ # For signals we can simply skip if prefix and suffix exists does not match
388
+ if (prefix is not None and not sinfo.id.name.startswith(prefix)) or (
389
+ suffix is not None and not sinfo.id.name.endswith(suffix)
390
+ ):
391
+ continue
392
+
393
+ metadata_dict = MessageToDict(
394
+ sinfo.metaData,
395
+ including_default_value_fields=True,
396
+ preserving_proto_field_name=True,
397
+ )
398
+ sig_dict = {
399
+ "signal": sinfo.id.name,
400
+ "namespace": network_info.namespace.name,
401
+ }
402
+ signal_names.append({**sig_dict, **metadata_dict})
395
403
 
396
404
  return signal_names
397
405
 
@@ -433,7 +441,7 @@ class Broker:
433
441
  def find_subscribed_signal(available_signal: List[Dict[str, str]]) -> List[str]:
434
442
  return list(filter(lambda s: available_signal["signal"] == s, subscribed_signals)) # type: ignore
435
443
 
436
- existing_signals = self.list_signal_names()
444
+ existing_signals = self.list_signal_names(prefix=None, suffix=None)
437
445
  existing_ns = set(map(lambda s: s["namespace"], existing_signals))
438
446
  ns_not_matching = []
439
447
  for ns in subscribed_namespaces:
@@ -5,7 +5,7 @@ import numbers
5
5
  import os
6
6
  import signal as os_signal
7
7
  from pathlib import Path
8
- from typing import Any, Dict, Iterable, List, TypedDict
8
+ from typing import Any, Dict, Iterable, List, TypedDict, Union
9
9
 
10
10
  import grpc
11
11
  import plotext as plt # type: ignore
@@ -30,23 +30,21 @@ class Signals(TypedDict):
30
30
  signal_values: Dict[Any, Any] = {}
31
31
 
32
32
 
33
- @app.command(name="list", help="List frame and signal metadata on broker")
33
+ @app.command(name="list")
34
34
  def list_signals(
35
35
  url: str = typer.Option(..., help="Broker URL", envvar="REMOTIVE_BROKER_URL"),
36
36
  api_key: str = typer.Option(None, help="Cloud Broker API-KEY or access token", envvar="REMOTIVE_BROKER_API_KEY"),
37
+ name_starts_with: Union[str, None] = typer.Option(None, help="Signal name prefix to include"),
38
+ name_ends_with: Union[str, None] = typer.Option(None, help="Signal name suffix to include"),
37
39
  ) -> None:
38
- signal_names(url, api_key)
39
-
40
+ """
41
+ List signal metadata on a broker
40
42
 
41
- @app.command(help="List signals names on broker", deprecated=True)
42
- def signal_names(
43
- url: str = typer.Option(..., help="Broker URL", envvar="REMOTIVE_BROKER_URL"),
44
- api_key: str = typer.Option(None, help="Cloud Broker API-KEY or access token", envvar="REMOTIVE_BROKER_API_KEY"),
45
- ) -> None:
43
+ Filter are inclusive so --name-starts-with and --name-ends-with will include name that matches both
44
+ """
46
45
  try:
47
46
  broker = Broker(url, api_key)
48
- # print("Listing available signals")
49
- available_signals = broker.list_signal_names()
47
+ available_signals = broker.list_signal_names(prefix=name_starts_with, suffix=name_ends_with)
50
48
  print(json.dumps(available_signals))
51
49
  except grpc.RpcError as rpc_error:
52
50
  ErrorPrinter.print_grpc_error(rpc_error)
@@ -0,0 +1,3 @@
1
+ from cli.cloud.auth.cmd import app
2
+
3
+ __all__ = ["app"]
@@ -0,0 +1,46 @@
1
+ import typer
2
+
3
+ from cli import settings
4
+ from cli.cloud.auth.login import login as do_login
5
+ from cli.cloud.rest_helper import RestHelper as Rest
6
+
7
+ from .. import auth_tokens
8
+
9
+ HELP = """
10
+ Manage how you authenticate with our cloud platform
11
+ """
12
+ app = typer.Typer(help=HELP)
13
+ app.add_typer(auth_tokens.app, name="tokens", help="Manage users personal access tokens")
14
+
15
+
16
+ @app.command(name="login")
17
+ def login() -> None:
18
+ """
19
+ Login to the cli using browser
20
+
21
+ This will be used as the current access token in all subsequent requests. This would
22
+ be the same as activating a personal access key or service-account access key.
23
+ """
24
+ do_login()
25
+
26
+
27
+ @app.command()
28
+ def whoami() -> None:
29
+ """
30
+ Validates authentication and fetches your user information
31
+ """
32
+ Rest.handle_get("/api/whoami")
33
+
34
+
35
+ @app.command()
36
+ def print_access_token() -> None:
37
+ """
38
+ Print current active access token
39
+ """
40
+ print(settings.read_secret_token())
41
+
42
+
43
+ @app.command(help="Clear access token")
44
+ def logout() -> None:
45
+ settings.clear_secret_token()
46
+ print("Access token removed")
@@ -4,23 +4,13 @@ from http.server import BaseHTTPRequestHandler, HTTPServer
4
4
  from threading import Thread
5
5
  from typing import Any
6
6
 
7
- import typer
8
7
  from typing_extensions import override
9
8
 
10
9
  from cli import settings
11
-
12
- from . import auth_tokens
13
- from .rest_helper import RestHelper as Rest
14
-
15
- HELP = """
16
- Manage how you authenticate with our cloud platform
17
- """
10
+ from cli.cloud.rest_helper import RestHelper as Rest
18
11
 
19
12
  httpd: HTTPServer
20
13
 
21
- app = typer.Typer(help=HELP)
22
- app.add_typer(auth_tokens.app, name="tokens", help="Manage users personal access tokens")
23
-
24
14
 
25
15
  class S(BaseHTTPRequestHandler):
26
16
  def _set_response(self) -> None:
@@ -55,46 +45,10 @@ def start_local_webserver(server_class: type = HTTPServer, handler_class: type =
55
45
  httpd = server_class(server_address, handler_class)
56
46
 
57
47
 
58
- #
59
- # CLI commands go here
60
- #
61
-
62
-
63
- @app.command(name="login")
64
48
  def login() -> None:
65
49
  """
66
- Login to the cli using browser
67
-
68
- This will be used as the current access token in all subsequent requests. This would
69
- be the same as activating a personal access key or service-account access key.
50
+ Initiate login using browser
70
51
  """
71
52
  start_local_webserver()
72
- webbrowser.open(
73
- f"{Rest.get_base_url()}/login?redirectUrl=http://localhost:{httpd.server_address[1]}",
74
- new=1,
75
- autoraise=True,
76
- )
77
-
53
+ webbrowser.open_new_tab(f"{Rest.get_base_url()}/login?redirectUrl=http://localhost:{httpd.server_address[1]}")
78
54
  httpd.serve_forever()
79
-
80
-
81
- @app.command()
82
- def whoami() -> None:
83
- """
84
- Validates authentication and fetches your user information
85
- """
86
- Rest.handle_get("/api/whoami")
87
-
88
-
89
- @app.command()
90
- def print_access_token() -> None:
91
- """
92
- Print current active access token
93
- """
94
- print(settings.read_secret_token())
95
-
96
-
97
- @app.command(help="Clear access token")
98
- def logout() -> None:
99
- settings.clear_secret_token()
100
- print("Access token removed")
@@ -1,9 +1,8 @@
1
1
  import typer
2
2
 
3
+ from cli.cloud import auth, brokers, configs, organisations, projects, recordings, sample_recordings, service_accounts, storage
3
4
  from cli.cloud.rest_helper import RestHelper
4
5
 
5
- from . import auth, brokers, configs, organisations, projects, recordings, sample_recordings, service_accounts, storage
6
-
7
6
  app = typer.Typer()
8
7
 
9
8
 
@@ -10,15 +10,14 @@ from cli.cloud.storage.uri_or_path import UriOrPath
10
10
  from cli.cloud.storage.uri_or_path import uri as uri_parser
11
11
  from cli.errors import ErrorPrinter
12
12
 
13
- app = typer.Typer(
14
- rich_markup_mode="rich",
15
- help="""
13
+ HELP = """
16
14
  Manage files ([yellow]Beta feature not available for all customers[/yellow])
17
15
 
18
16
  Copy file from local to remote storage and vice versa, list and delete files.
19
17
 
20
- """,
21
- )
18
+ """
19
+
20
+ app = typer.Typer(rich_markup_mode="rich", help=HELP)
22
21
 
23
22
 
24
23
  @app.command(name="ls")
@@ -0,0 +1,113 @@
1
+ from __future__ import annotations
2
+
3
+ from os import PathLike
4
+ from pathlib import PurePosixPath
5
+ from urllib.parse import urlparse
6
+
7
+
8
+ class InvalidURIError(Exception):
9
+ """Raised when an invalid URI is encountered"""
10
+
11
+
12
+ class JoinURIError(Exception):
13
+ """Raised when an error occurs while joining URIs"""
14
+
15
+
16
+ class URI:
17
+ """
18
+ Custom type for rcs (Remotive Cloud Storage) URIs.
19
+
20
+ The URI format follows the pattern: rcs://bucket/path/to/resource
21
+ """
22
+
23
+ scheme: str
24
+ """The URI scheme (default: 'rcs')"""
25
+
26
+ path: str
27
+ """The full path component, including leading slash"""
28
+
29
+ filename: str
30
+ """The name of the file or last path segment"""
31
+
32
+ bucket: str
33
+ """The first path segment after the leading slash"""
34
+
35
+ parent: URI
36
+ """The parent URI. If at root, returns a copy of itself."""
37
+
38
+ def __init__(self, value: str, scheme: str = "rcs"):
39
+ """
40
+ Create a new URI.
41
+
42
+ Args:
43
+ value: The URI string in format "scheme://path/to/resource"
44
+ scheme: The URI scheme (default: 'rcs')
45
+
46
+ Raises:
47
+ InvalidURIError: If the URI format is invalid
48
+ """
49
+ self._raw = value
50
+ self.scheme = scheme
51
+
52
+ parsed = urlparse(value)
53
+ if parsed.scheme != self.scheme:
54
+ raise InvalidURIError(f"Invalid URI scheme. Expected '{self.scheme}://', got '{parsed.scheme}://'")
55
+ if parsed.netloc.startswith((".", "-", "#", " ", "/", "\\")):
56
+ raise InvalidURIError(f"Invalid URI. Path cannot start with invalid characters: '{value}'")
57
+ if not parsed.netloc and parsed.path == "/":
58
+ raise InvalidURIError(f"Invalid URI: '{value}'")
59
+
60
+ self.path = f"/{parsed.netloc}{parsed.path}" if parsed.netloc else f"/{parsed.path}"
61
+
62
+ self._posix_path = PurePosixPath(self.path)
63
+ self.filename = self._posix_path.name
64
+ self.bucket = self._posix_path.parts[1] if len(self._posix_path.parts) > 1 else ""
65
+
66
+ if self._posix_path == PurePosixPath("/"):
67
+ self.parent = self
68
+ else:
69
+ parent_path = self._posix_path.parent
70
+ new_uri = f"{self.scheme}://{str(parent_path)[1:]}"
71
+ self.parent = URI(new_uri, scheme=self.scheme)
72
+
73
+ def is_dir(self) -> bool:
74
+ """Check if the URI points to a directory."""
75
+ return self.path.endswith("/")
76
+
77
+ def __truediv__(self, other: PathLike[str] | str) -> URI:
78
+ """
79
+ Join this URI with another path component.
80
+
81
+ Args:
82
+ other: Path component to join
83
+
84
+ Returns:
85
+ A new URI with the joined path
86
+
87
+ Raises:
88
+ JoinURIError: If trying to join an absolute path
89
+ TypeError: If the path component is not a string or PathLike
90
+ """
91
+ if str(other).startswith("/"):
92
+ raise JoinURIError(f"Cannot join absolute path '{other}' to URI")
93
+
94
+ is_dir = str(other).endswith("/")
95
+ new_path = self._posix_path / other
96
+
97
+ for part in new_path.parts:
98
+ if part == "..":
99
+ new_path = new_path.parent
100
+ elif part != ".":
101
+ new_path = new_path / part
102
+
103
+ new_uri = f"{self.scheme}://{new_path.relative_to('/')}" # we need to strip the starting '/'
104
+ new_uri = new_uri if not is_dir else f"{new_uri}/" # and append slash if the added path was a dir
105
+ return URI(new_uri, scheme=self.scheme)
106
+
107
+ def __str__(self) -> str:
108
+ """Return the original URI string."""
109
+ return self._raw
110
+
111
+ def __repr__(self) -> str:
112
+ """Return the original URI string."""
113
+ return f"URI({self._raw})"
File without changes
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "remotivelabs-cli"
3
- version = "0.0.32"
3
+ version = "0.0.34"
4
4
  description = "CLI for operating RemotiveCloud and RemotiveBroker"
5
5
  authors = ["Johan Rask <johan.rask@remotivelabs.com>"]
6
6
  readme = "README.md"
@@ -11,8 +11,8 @@ packages = [{ include = "cli" }]
11
11
  remotive = "cli.remotive:app"
12
12
 
13
13
  [tool.poetry.dependencies]
14
+ python = ">=3.8"
14
15
  trogon = ">=0.5.0"
15
- python = ">=3.8,<3.12"
16
16
  typer = "0.12.5"
17
17
  remotivelabs-broker = "~=0.1.17"
18
18
  rich = "~=13.7.0"
@@ -29,6 +29,8 @@ types-requests = "^2.32.0.20240622"
29
29
  [tool.poetry.group.test.dependencies]
30
30
  pytest = "^8.3"
31
31
  pytest-cov = "^5.0"
32
+ semver = "^3.0.2"
33
+ friendlywords = "^1.1.3"
32
34
 
33
35
  [tool.poetry.group.lint.dependencies]
34
36
  ruff = "^0.6.5"
@@ -36,14 +38,6 @@ pylint = "^3.2.7"
36
38
  pylint-protobuf = "^0.22.0"
37
39
  mypy = "^1.11"
38
40
 
39
- [tool.poe.tasks]
40
- test = [{ cmd = "pytest --cov=cli ." }]
41
- pylint = [{ cmd = "pylint ." }]
42
- lint = [{ cmd = "ruff check ." }, { cmd = "ruff format --check --diff ." }]
43
- format = [{ cmd = "ruff format ." }, { cmd = "ruff check --fix ." }]
44
- mypy = [{ cmd = "mypy -p cli" }]
45
- check = ["lint", "pylint", "mypy"]
46
-
47
41
  [tool.pytest.ini_options]
48
42
  markers = ["itests: run tests with custom flag"]
49
43
 
@@ -70,6 +64,7 @@ select = [
70
64
  recursive = true
71
65
  ignore-paths = '^.venv/.*$'
72
66
  load-plugins = ["pylint_protobuf"]
67
+ ignored-modules = ["friendlywords"]
73
68
 
74
69
  [tool.pylint.format]
75
70
  max-line-length = 140
@@ -104,6 +99,9 @@ hide_error_codes = false
104
99
  module = "someip.*"
105
100
  ignore_missing_imports = true
106
101
 
102
+ [[tool.mypy.overrides]]
103
+ module = "friendlywords.*"
104
+ ignore_missing_imports = true
107
105
 
108
106
  [build-system]
109
107
  requires = ["poetry-core"]
@@ -1,59 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from os import PathLike
4
- from pathlib import PurePosixPath
5
- from urllib.parse import urlparse
6
-
7
-
8
- class InvalidURIError(Exception):
9
- """Raised when an invalid URI is encountered"""
10
-
11
-
12
- class JoinURIError(Exception):
13
- """Raised when an error occurs while joining URIs"""
14
-
15
-
16
- class URI:
17
- """
18
- Custom type for rcs (Remotive Cloud Storage) URIs.
19
- """
20
-
21
- def __init__(self, value: str, scheme: str = "rcs"):
22
- self.original_uri = value
23
- self.scheme = scheme
24
-
25
- parsed = urlparse(value)
26
- if parsed.scheme != self.scheme:
27
- raise InvalidURIError(f"Invalid URI scheme. Expected '{self.scheme}://', got '{parsed.scheme}://'")
28
- if parsed.netloc.startswith((".", "-", "#", " ", "/", "\\")):
29
- raise InvalidURIError(f"Invalid URI. Path cannot start with invalid characters: '{value}'")
30
- if not parsed.netloc and parsed.path == "/":
31
- raise InvalidURIError(f"Invalid URI: '{value}'")
32
-
33
- self.path = f"/{parsed.netloc}{parsed.path}" if parsed.netloc else f"/{parsed.path}"
34
-
35
- self._posix_path = PurePosixPath(self.path)
36
- self.filename = self._posix_path.name
37
-
38
- def is_dir(self) -> bool:
39
- return self.path.endswith("/")
40
-
41
- def __truediv__(self, other: PathLike[str] | str) -> URI:
42
- """Returns a new URI object with the joined path"""
43
- if str(other).startswith("/"):
44
- raise JoinURIError(f"Cannot join absolute path '{other}' to URI")
45
-
46
- new_path = self._posix_path / other
47
-
48
- # handle relative paths
49
- for part in new_path.parts:
50
- if part == "..":
51
- new_path = new_path.parent
52
- elif part != ".":
53
- new_path = new_path / part
54
-
55
- new_uri = f"{self.scheme}://{new_path.relative_to('/')}" # we need to strip the starting '/'
56
- return URI(new_uri, scheme=self.scheme)
57
-
58
- def __str__(self) -> str:
59
- return self.original_uri