charmlibs-pathops 0.0.0a1__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.
@@ -0,0 +1,685 @@
1
+ # Copyright 2024 Canonical Ltd.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Type-checking exclusive definitions."""
16
+
17
+ from __future__ import annotations
18
+
19
+ import typing
20
+
21
+ from . import _constants
22
+
23
+ if typing.TYPE_CHECKING:
24
+ import os
25
+ from typing import Generator, Sequence
26
+
27
+ from typing_extensions import Self
28
+
29
+
30
+ # based on typeshed.stdlib.pathlib.PurePath
31
+ # https://github.com/python/typeshed/blob/main/stdlib/pathlib.pyi#L29
32
+ class PathProtocol(typing.Protocol):
33
+ """The protocol implemented by :class:`ContainerPath` and :class:`LocalPath`.
34
+
35
+ Use this class in type annotations where either :class:`ContainerPath` or
36
+ :class:`LocalPath` is acceptable. This will result in both correct type checking
37
+ and useful autocompletions.
38
+
39
+ While using a union type will also give correct type checking results, it provides less
40
+ useful autocompletions, as most editors will autocomplete methods and attributes that *any*
41
+ of the union members have, rather than only those that *all* of the union members have.
42
+
43
+ :class:`str` follows the :mod:`pathlib` convention and returns the string representation of
44
+ the path. :class:`ContainerPath` return the string representation of the path in the remote
45
+ filesystem. The string representation is suitable for use with system calls (on the correct
46
+ local or remote system) and Pebble layers.
47
+
48
+ Comparison methods compare by path. A :class:`ContainerPath` is only comparable to another
49
+ object if it is also a :class:`ContainerPath` on the same :class:`ops.Container`. If this is
50
+ not the case, then equality is ``False`` and other comparisons are :class:`NotImplemented`.
51
+
52
+ Protocol implementers are hashable.
53
+ """
54
+
55
+ #############################
56
+ # protocol PurePath methods #
57
+ #############################
58
+
59
+ def __hash__(self) -> int: ...
60
+
61
+ # comparison methods
62
+ def __lt__(self, other: Self) -> bool: ...
63
+
64
+ def __le__(self, other: Self) -> bool: ...
65
+
66
+ def __gt__(self, other: Self) -> bool: ...
67
+
68
+ def __ge__(self, other: Self) -> bool: ...
69
+
70
+ def __eq__(self, other: object, /) -> bool: ...
71
+
72
+ def __truediv__(self, key: str | os.PathLike[str]) -> Self:
73
+ """Return a new instance of the same type, with the other operand appended to its path.
74
+
75
+ Used as ``protocol_implementor / str_or_pathlike``.
76
+
77
+ .. note::
78
+ ``__rtruediv__`` is currently not part of this protocol, as
79
+ :class:`ContainerPath` objects only support absolute paths.
80
+ """
81
+ ...
82
+
83
+ def __str__(self) -> str: ...
84
+
85
+ def as_posix(self) -> str:
86
+ """Return the string representation of the path.
87
+
88
+ Exactly like :class:`pathlib.Path` for local paths. Following this convention,
89
+ remote filesystem paths such as :class:`ContainerPath` return the string representation
90
+ of the path in the remote filesystem. The string representation is suitable for
91
+ use with system calls (on the correct local or remote system) and Pebble layers.
92
+
93
+ Identical to :class:`str`, since we only support posix systems.
94
+ """
95
+ ...
96
+
97
+ def is_absolute(self) -> bool:
98
+ """Return whether the path is absolute (has a root).
99
+
100
+ .. note::
101
+ Currently always ``True`` for :class:`ContainerPath`.
102
+ """
103
+ ...
104
+
105
+ # NOTE: Not supported -- (Python 3.12) ``case_sensitive`` keyword argument
106
+ def match(self, path_pattern: str) -> bool:
107
+ """Return whether this path matches the given pattern.
108
+
109
+ If the pattern is relative, matching is done from the right; otherwise, the entire path is
110
+ matched. The recursive wildcard ``'**'`` is **not** supported by this method. Matching is
111
+ always case-sensitive.
112
+ """
113
+ ...
114
+
115
+ def with_name(self, name: str) -> Self:
116
+ """Return a new instance of the same type, with the path name replaced.
117
+
118
+ The name is the last component of the path, including any suffixes.
119
+ """
120
+ ...
121
+
122
+ def with_suffix(self, suffix: str) -> Self:
123
+ """Return a new instance of the same type, with the path suffix replaced.
124
+
125
+ Args:
126
+ suffix: Must start with a ``'.'``, unless it is an empty string, in which case
127
+ any existing suffix will be removed entirely.
128
+
129
+ Returns:
130
+ A new instance of the same type, updated as follows. If it contains no ``'.'``,
131
+ or ends with a ``'.'``, the ``suffix`` argument is appended to its name. Otherwise,
132
+ the last ``'.'`` and any trailing content is replaced with the ``suffix`` argument.
133
+ """
134
+ ...
135
+
136
+ def joinpath(self, *other: str | os.PathLike[str]) -> Self:
137
+ """Return a new instance of the same type, with its path joined with the arguments.
138
+
139
+ Args:
140
+ other: Any number of :class:`str` or :class:`os.PathLike` objects.
141
+
142
+ Returns:
143
+ A new instance of the same type, updated as follows. For each item in other,
144
+ if it is a relative path, it is appended to the current path. If it is an absolute
145
+ path, it replaces the current path.
146
+
147
+ .. warning::
148
+ :class:`ContainerPath` is not :class:`os.PathLike`. A :class:`ContainerPath` instance
149
+ is not a valid value for ``other``, and will result in an error.
150
+ """
151
+ ...
152
+
153
+ @property
154
+ def parents(self) -> Sequence[Self]:
155
+ """A sequence of this path's logical parents. Each parent is an instance of this type."""
156
+ ...
157
+
158
+ @property
159
+ def parent(self) -> Self:
160
+ """The logical parent of this path. An instance of this type."""
161
+ ...
162
+
163
+ @property
164
+ def parts(self) -> tuple[str, ...]:
165
+ """A sequence of the components in the filesystem path."""
166
+ ...
167
+
168
+ @property
169
+ def name(self) -> str:
170
+ """The final path component, or an empty string if this is the root path."""
171
+ ...
172
+
173
+ @property
174
+ def suffix(self) -> str:
175
+ """The path name's last suffix (if it has any) including the leading ``'.'``.
176
+
177
+ If the path name doesn't have a suffix, the result is an empty string.
178
+ """
179
+ ...
180
+
181
+ @property
182
+ def suffixes(self) -> list[str]:
183
+ r"""A list of the path name's suffixes.
184
+
185
+ Each suffix includes the leading ``'.'``.
186
+
187
+ If the path name doesn't have any suffixes, the result is an empty list.
188
+ """
189
+ ...
190
+
191
+ @property
192
+ def stem(self) -> str:
193
+ """The path name, minus its last suffix.
194
+
195
+ Where :meth:`name` == :meth:`stem` + :meth:`suffix`
196
+ """
197
+ ...
198
+
199
+ #########################
200
+ # protocol Path methods #
201
+ #########################
202
+
203
+ # NOTE: Not supported -- (Python 3.13) ``newline`` argument
204
+ def read_text(self) -> str:
205
+ """Read the corresponding local or remote filesystem path and return the string contents.
206
+
207
+ Compared to :meth:`pathlib.Path.read_text`, this method drops the ``encoding`` and
208
+ ``errors`` args to simplify the API.
209
+
210
+ Returns:
211
+ The UTF-8 decoded contents of the file as a :class:`str`.
212
+
213
+ Raises:
214
+ FileNotFoundError: If this path does not exist.
215
+ PermissionError: If the local or remote user does not have read permissions.
216
+ UnicodeError: If the file's contents are not valid UTF-8.
217
+ PebbleConnectionError: If the remote container cannot be reached.
218
+ """
219
+ ...
220
+
221
+ def read_bytes(self) -> bytes:
222
+ """Read the corresponding local or remote filesystem path and return the binary contents.
223
+
224
+ Returns:
225
+ The file's raw contents as :class:`bytes`.
226
+
227
+ Raises:
228
+ FileNotFoundError: If this path does not exist.
229
+ PermissionError: If the local or remote user does not have read permissions.
230
+ PebbleConnectionError: If the remote container cannot be reached.
231
+ """
232
+ ...
233
+
234
+ def iterdir(self) -> Generator[Self]:
235
+ """Yield objects of the same type corresponding to the directory's contents.
236
+
237
+ There are no guarantees about the order of the children. The special entries
238
+ ``'.'`` and ``'..'`` are not included.
239
+
240
+ Raises:
241
+ FileNotFoundError: If this path does not exist.
242
+ NotADirectoryError: If this path is not a directory.
243
+ PermissionError: If the local or remote user does not have appropriate permissions.
244
+ PebbleConnectionError: If the remote container cannot be reached.
245
+ """
246
+ ...
247
+
248
+ # NOTE: Not supported -- (Python 3.12) ``case_sensitive`` argument
249
+ # NOTE: Not supported -- (Python 3.13) ``pattern`` can be path-like
250
+ # NOTE: Not supported -- (Python 3.13) ``recurse_symlinks``
251
+ def glob(self, pattern: str) -> Generator[Self]:
252
+ r"""Iterate over this directory and yield all paths matching the provided pattern.
253
+
254
+ For example, ``path.glob('*.txt')``, ``path.glob('*/foo.txt')``.
255
+
256
+ .. warning::
257
+ Recursive matching, using the ``'**'`` pattern, is not supported by
258
+ :meth:`ContainerPath.glob`.
259
+
260
+ Args:
261
+ pattern: Must be relative, meaning it cannot begin with ``'/'``.
262
+ Matching is case-sensitive.
263
+
264
+ Returns:
265
+ A generator yielding objects of the same type as this path, corresponding to those
266
+ of its children which match the pattern. If this path is not a directory, there will
267
+ be no matches.
268
+
269
+ Raises:
270
+ FileNotFoundError: If this path does not exist.
271
+ NotImplementedError: If pattern is an absolute path, or (in the case of
272
+ :class:`ContainerPath`) if it uses the ``'**'`` pattern.
273
+ PermissionError: If the local or remote user does not have appropriate permissions.
274
+ ValueError: If the pattern is invalid.
275
+ PebbleConnectionError: If the remote container cannot be reached.
276
+ """
277
+ ...
278
+
279
+ def owner(self) -> str:
280
+ """Return the user name of the file owner.
281
+
282
+ Raises:
283
+ FileNotFoundError: If the path does not exist.
284
+ PebbleConnectionError: If the remote container cannot be reached.
285
+ """
286
+ ...
287
+
288
+ def group(self) -> str:
289
+ """Return the group name of the file.
290
+
291
+ Raises:
292
+ FileNotFoundError: If the path does not exist.
293
+ PebbleConnectionError: If the remote container cannot be reached.
294
+ """
295
+ ...
296
+
297
+ # NOTE: Not supported -- (Python 3.12) ``follow_symlinks`` argument (default=True)
298
+ def exists(self) -> bool:
299
+ """Whether this path exists.
300
+
301
+ Will follow symlinks to determine if the symlink target exists. This means that this
302
+ method will return ``False`` for a broken symlink.
303
+
304
+ Raises:
305
+ PebbleConnectionError: If the remote container cannot be reached.
306
+ """
307
+ ...
308
+
309
+ # NOTE: Not supported -- (Python 3.13) ``follow_symlinks`` argument (default=True)
310
+ def is_dir(self) -> bool:
311
+ """Whether this path exists and is a directory.
312
+
313
+ Will follow symlinks to determine if the symlink target exists and is a directory.
314
+
315
+ Raises:
316
+ PebbleConnectionError: If the remote container cannot be reached.
317
+ """
318
+ ...
319
+
320
+ # NOTE: Not supported -- (Python 3.13) ``follow_symlinks`` argument (default=True)
321
+ def is_file(self) -> bool:
322
+ """Whether this path exists and is a regular file.
323
+
324
+ Will follow symlinks to determine if the symlink target exists and is a regular file.
325
+
326
+ Raises:
327
+ PebbleConnectionError: If the remote container cannot be reached.
328
+ """
329
+ ...
330
+
331
+ def is_fifo(self) -> bool:
332
+ """Whether this path exists and is a named pipe (also called a FIFO).
333
+
334
+ Will follow symlinks to determine if the symlink target exists and is a named pipe.
335
+
336
+ Raises:
337
+ PebbleConnectionError: If the remote container cannot be reached.
338
+ """
339
+ ...
340
+
341
+ def is_socket(self) -> bool:
342
+ """Whether this path exists and is a socket.
343
+
344
+ Will follow symlinks to determine if the symlink target exists and is a socket.
345
+
346
+ Raises:
347
+ PebbleConnectionError: If the remote container cannot be reached.
348
+ """
349
+ ...
350
+
351
+ ##################################################
352
+ # protocol Path methods with extended signatures #
353
+ ##################################################
354
+
355
+ def write_bytes(
356
+ self,
357
+ data: bytes,
358
+ *,
359
+ mode: int = _constants.DEFAULT_WRITE_MODE,
360
+ user: str | None = None,
361
+ group: str | None = None,
362
+ ) -> int:
363
+ """Write the provided data to the corresponding path.
364
+
365
+ Compared to :meth:`pathlib.Path.write_bytes`, this method adds ``mode``, ``user``
366
+ and ``group`` args, which are set on file creation.
367
+
368
+ Args:
369
+ data: The bytes to write, typically a :class:`bytes` object, but may also be a
370
+ :class:`bytearray` or :class:`memoryview`.
371
+ mode: The permissions to set on the file. Defaults to 0o644 (-rw-rw-r--).
372
+ user: The name of the user to set for the file.
373
+ group: The name of the group to set for the file.
374
+
375
+ Returns:
376
+ The number of bytes written.
377
+
378
+ Raises:
379
+ FileNotFoundError: if the parent directory does not exist.
380
+ LookupError: if the user or group is unknown.
381
+ NotADirectoryError: if the parent exists as a non-directory file.
382
+ PermissionError: if the user does not have permissions for the operation.
383
+ PebbleConnectionError: if the remote Pebble client cannot be reached.
384
+ """
385
+ ...
386
+
387
+ # NOTE: Not supported -- (Python 3.10) ``newline`` argument
388
+ def write_text(
389
+ self,
390
+ data: str,
391
+ *,
392
+ mode: int = _constants.DEFAULT_WRITE_MODE,
393
+ user: str | None = None,
394
+ group: str | None = None,
395
+ ) -> int:
396
+ """Write the provided string to the corresponding path.
397
+
398
+ Compared to :meth:`pathlib.Path.write_text`, this method drops the ``encoding`` and
399
+ ``errors`` args to simplify the API. This method adds ``mode``, ``user`` and ``group``
400
+ args, which are set on file creation.
401
+
402
+ Args:
403
+ data: The string to write. Newlines are not modified on writing.
404
+ mode: The permissions to set on the file. Defaults to 0o644 (-rw-rw-r--).
405
+ user: The name of the user to set for the file.
406
+ group: The name of the group to set for the file.
407
+
408
+ Returns:
409
+ The number of bytes written.
410
+
411
+ Raises:
412
+ FileNotFoundError: if the parent directory does not exist.
413
+ LookupError: if the user or group is unknown.
414
+ NotADirectoryError: if the parent exists as a non-directory file.
415
+ PermissionError: if the user does not have permissions for the operation.
416
+ UnicodeError: if the provided data is not valid UTF-8.
417
+ PebbleConnectionError: if the remote Pebble client cannot be reached.
418
+ """
419
+ ...
420
+
421
+ def mkdir(
422
+ self,
423
+ mode: int = _constants.DEFAULT_MKDIR_MODE,
424
+ parents: bool = False,
425
+ exist_ok: bool = False,
426
+ *,
427
+ user: str | None = None,
428
+ group: str | None = None,
429
+ ) -> None:
430
+ """Create a new directory at the corresponding path.
431
+
432
+ Compared to :meth:`pathlib.Path.mkdir`, this method adds ``user`` and ``group`` args.
433
+ These are used to set the ownership of the created directory. Any created parents
434
+ will not have their ownership set.
435
+
436
+ Args:
437
+ mode: The permissions to set on the created directory. Any parents created will have
438
+ their permissions set to the default value of 0o755 (drwxr-xr-x).
439
+ parents: Whether to create any missing parent directories as well. If ``False``
440
+ (default) and a parent directory does not exist, a :class:`FileNotFound` error will
441
+ be raised.
442
+ exist_ok: Whether to raise an error if the directory already exists.
443
+ If ``False`` (default) and the directory already exists,
444
+ a :class:`FileExistsError` will be raised.
445
+ user: The name of the user to set for the directory.
446
+ group: The name of the group to set for the directory.
447
+
448
+ Raises:
449
+ FileExistsError: if the directory already exists and ``exist_ok`` is ``False``.
450
+ FileNotFoundError: if the parent directory does not exist and ``parents`` is ``False``.
451
+ LookupError: if the user or group is unknown.
452
+ NotADirectoryError: if the parent exists as a non-directory file.
453
+ PermissionError: if the local user does not have permissions for the operation.
454
+ PebbleConnectionError: if the remote Pebble client cannot be reached.
455
+ """
456
+ ...
457
+
458
+
459
+ ################################################
460
+ # pathlib methods not included in the protocol #
461
+ ################################################
462
+
463
+ # TODO: clean up these comments structurally and informationally
464
+
465
+ #############################
466
+ # protocol PurePath methods #
467
+ #############################
468
+
469
+ # constructors
470
+ # ContainerPath constructor will differ from pathlib.Path constructor
471
+ # not part of the protocol
472
+ # def __new__(cls, *args: _StrPath, **kwargs: object) -> Self: ...
473
+ # __new__ signature is version dependent
474
+ # def __init__(self, *args): ...
475
+
476
+ # def __reduce__(self): ...
477
+ # ops.Container isn't pickleable itself, but we can provide a custom constructor
478
+ # for ContainerPath that works with the unpickling protocol, and will attempt to
479
+ # make a Container object connected to the appropriate pebble socket
480
+ # for simplicity this will be omitted from v1 unless requested
481
+
482
+ # def __rtruediv__(self, key: str | os.PathLike[str]) -> Self: ...
483
+ # omitted from v1 protocol
484
+ # doesn't seem worth supporting until (if) ContainerPath gets relative paths
485
+ #
486
+ # when we have relative paths, it will be meaningful to support the following:
487
+ # def __truediv__(self, key: str | os.PathLike[str] | Self) -> Self: ...
488
+ # def __rtruediv__(self, key: str | os.PathLike[str] | Self) -> Self: ...
489
+ # `ContainerPath / (str or pathlib.Path)`, or `(str or pathlib.Path) / containerPath`
490
+ # will result in a new ContainerPath with the same container.
491
+ # `ContainerPath / ContainerPath` is an error if the containers are not the same,
492
+ # otherwise it too results in a new ContainerPath with the same container.
493
+
494
+ # def __fspath__(self) -> str: ...
495
+ # we don't want ContainerPath to be os.PathLike
496
+
497
+ # def __bytes__(self) -> bytes: ...
498
+ # we don't want ContainerPath to be mistakenly used like a pathlib.Path
499
+
500
+ # URIs
501
+ # this doesn't seem useful and is potentially confusing,so it won't be implemented
502
+ # likewise, the constructor (added in 3.13) won't be implemented
503
+ # def as_uri(self) -> str: ...
504
+ # @classmethod
505
+ # def from_uri(uri: str) -> Self: ...
506
+
507
+ # def is_reserved(self) -> bool: ...
508
+ # this will always return False with a PosixPath. Since we assume a Linux container
509
+ # so let's just drop it from the protocol for now
510
+
511
+ # def full_match(self, pattern: str, * case_sensitive: bool = False) -> bool: ...
512
+ # 3.13+
513
+ # not part of the protocol but may eventually be provided on ContainerPath
514
+ # to ease compatibility with pathlib.Path on 3.13+
515
+
516
+ # def relative_to(self, other: _StrPath, /) -> Self: ...
517
+ # this produces relative paths, which we shouldn't be using in any code designed to
518
+ # be compatible with both machines and containers, since pebble will error on any
519
+ # relative paths at runtime -- if users want to work with relative paths, I think
520
+ # they should explicitly work with a PurePath rather than a ContainerPath, and then
521
+ # construct the ContainerPath when they have the absolute path they need
522
+ #
523
+ # Python 3.12 deprecates the below signature, to be dropped in 3.14
524
+ # def relative_to(self, *other: _StrPath) -> Self: ...
525
+ # to ease future compatibility, we'd just drop support for the old signature
526
+ # from the protocol now if it was included
527
+ #
528
+ # Python 3.12 further modifies the signature with an additional keyword argument
529
+ # def relative_to(self, other: _StrPath, walk_up: bool = False) -> Self: ...
530
+ # this would not part of the protocol but could eventually be provided on
531
+ # ContainerPath to ease compatibility with pathlib.Path on 3.12+ if we someday
532
+ # support relative paths
533
+
534
+ # def is_relative_to(self, other: _StrPath) -> Self: ... # 3.9+
535
+ # not part of protocol but can be provided on ContainerPath implementation
536
+ # to ease compatibility with pathlib.Path on 3.9+
537
+
538
+ # def with_stem(self, stem: str) -> Self: ... # 3.9+
539
+ # not part of protocol but can be provided on ContainerPath implementation
540
+ # to ease compatibility with pathlib.Path on 3.9+
541
+ # could be added to the protocol if we're happy for LocalPath to double as backports
542
+
543
+ # def with_segments(self, *pathsegments: _StrPath) -> Self: ...
544
+ # required for 3.12+ subclassing machinery
545
+ # not part of the protocol (otherwise LocalPath would have to backport it)
546
+ # but it is a useful method -- given a ContainerPath with some container,
547
+ # you can make another path with the same container cleanly, so it'll be implemented
548
+ # on ContainerPath
549
+
550
+ # @property
551
+ # def drive(self) -> str: ...
552
+ # will always be '' for Posix -- maybe drop it from the protocol
553
+ # so users get more useful autocompletions?
554
+
555
+ # @property
556
+ # def root(self) -> str: ...
557
+ # potentially error prone -- ContainerPath.root / Path('foo') is not a ContainerPath
558
+
559
+ # @property
560
+ # def anchor(self) -> str: ...
561
+ # this is drive + root
562
+
563
+ #########################
564
+ # protocol Path methods #
565
+ #########################
566
+
567
+ # remove
568
+ # these methods are problematic because Pebble
569
+ # 1. doesn't let us distinguish in advance between dirs and symlinks to them
570
+ # 2. doesn't provide a way not to remove an empty directory
571
+ #
572
+ # If provided, would have to also remove symlinks to directories
573
+ # def rmdir(self) -> None: ...
574
+ #
575
+ # If provided, would either have to also remove empty directories
576
+ # or be unable to remove symlinks to directories
577
+ # def unlink(self, missing_ok: bool = False) -> None: ...
578
+
579
+ # recursive glob is problematic because Pebble doesn't tell us whether something is a symlink
580
+ # so we can easily recurse until we hit Pebble's api limit
581
+ # def rglob(
582
+ # self,
583
+ # pattern: str, # support for _StrPath added in 3.13
584
+ # # *,
585
+ # # case_sensitive: bool = False, # added in 3.12
586
+ # # recurse_symlinks: bool = False, # added in 3.13
587
+ # ) -> Generator[Self]: ...
588
+
589
+ # walk was only added in 3.12 -- let's not support this for now, as we'd need to
590
+ # implement the walk logic for LocalPath as well as whatever we do for ContainerPath
591
+ # (which will also be a bit trickier being unable to distinguish symlinks as dirs)
592
+ # While Path.walk wraps os.walk, there are still ~30 lines of pathlib code we'd need
593
+ # to vendor for LocalPath.walk
594
+ # def walk(
595
+ # self,
596
+ # top_down: bool = True,
597
+ # on_error: typing.Callable[[OSError], None] | None = None,
598
+ # follow_symlinks: bool = False, # NOTE: ContainerPath runtime error if True
599
+ # ) -> typing.Iterator[tuple[Self, list[str], list[str]]]:
600
+ # # TODO: if we add a follow_symlinks option to Pebble's list_files API, we can
601
+ # # then support follow_symlinks=True on supported Pebble (Juju) versions
602
+ # ...
603
+
604
+ # def stat(self) -> os.stat_result: ...
605
+ # stat follows symlinks to return information about the target
606
+ # this may not be in v1, because we can only provide best effort completion on the
607
+ # pebble side -- for example, we can't distinguish block and char devices, and we can't
608
+ # detect if something is a symlink. Maybe we can provide a top-level fileinfo helper
609
+
610
+ # def lstat(self) -> os.stat_result: ...
611
+ # lstat tells you about the symlink rather than its target
612
+ # pebble only tells you about the target
613
+ # TODO: support if we add follow_symlinks to Pebble's list_files API
614
+
615
+ # def is_mount(self) -> bool: ...
616
+ # pebble doesn't support this
617
+
618
+ # def is_symlink(self) -> bool: ...
619
+ # pebble doesn't support this
620
+
621
+ # def is_junction(self) -> bool: ...
622
+ # 3.12
623
+ # this will always be False in ContainerPath since we assume a Linux container
624
+ # so let's just drop it from the protocol for now
625
+
626
+ # is_block_device and is_char_device are problematic because pebble only tells us if
627
+ # it's a device at all. We can provide an is_device module level helper if needed.
628
+ # def is_block_device(self) -> bool: ...
629
+ # def is_char_device(self) -> bool: ...
630
+
631
+ ################################################################################
632
+ # these concrete methods are currently ruled out due to lack of Pebble support #
633
+ ################################################################################
634
+
635
+ # def chmod
636
+ # pebble sets mode on creation
637
+ # can't provide a separate method
638
+ # needs to be argument for other functions
639
+ # (same treatment needed for chown)
640
+
641
+ # link creation, modification, target retrieval
642
+ # pebble doesn't support link manipulation
643
+ # def hardlink_to
644
+ # def symlink_to
645
+ # def lchmod
646
+ # def readlink
647
+ # def resolve
648
+
649
+ # def samefile
650
+ # pebble doesn't return device and i-node number
651
+ # can't provide the same semantics
652
+
653
+ # def open
654
+ # the semantics would be different due to needing to make a local copy
655
+
656
+ # def touch
657
+ # would have to pull down the existing file and push it back up just to set mtime
658
+
659
+ ##################
660
+ # relative paths #
661
+ ##################
662
+
663
+ # OPINION: we shouldn't support relative paths in v1 (if ever)
664
+ #
665
+ # if we support relative paths, we'd need to implicitly call absolute before every
666
+ # call that goes to pebble, and it's not clear whether it's a good idea to implement
667
+ # cwd, which absolute would depend on -- we'd have to pebble exec cwd, which wouldn't
668
+ # work in certain images (bare rocks)
669
+ #
670
+ # I think it would be fine for v1 to only support absolute paths, raising an error
671
+ # on file operations with relative paths
672
+
673
+ # the following methods would require us to either hardcode cwd or use a pebble.exec
674
+ # def cwd
675
+ # typically /root in container
676
+ # do we need to query this each time? can we hardcode it?
677
+ # def absolute
678
+ # interpret relative to cwd
679
+
680
+ # the following methods would require us to either hardcode home or use a pebble.exec
681
+ # def home
682
+ # typically /root in container
683
+ # do we need to query this each time? can we hardcode it?
684
+ # def expanduser
685
+ # '~' in parts becomes self.home
@@ -0,0 +1 @@
1
+ 0.0.0a1
File without changes