11import contextlib
22import functools
3+ from abc import ABCMeta
4+ from abc import abstractmethod
5+ from typing import Iterable
6+ from typing import List
37from typing import Optional
48
9+ from vdirsyncer .vobject import Item
10+
511from .. import exceptions
612from ..utils import uniq
713
814
915def mutating_storage_method (f ):
16+ """Wrap a method and fail if the instance is readonly."""
17+
1018 @functools .wraps (f )
1119 async def inner (self , * args , ** kwargs ):
1220 if self .read_only :
@@ -16,8 +24,10 @@ async def inner(self, *args, **kwargs):
1624 return inner
1725
1826
19- class StorageMeta (type ):
27+ class StorageMeta (ABCMeta ):
2028 def __init__ (cls , name , bases , d ):
29+ """Wrap mutating methods to fail if the storage is readonly."""
30+
2131 for method in ("update" , "upload" , "delete" ):
2232 setattr (cls , method , mutating_storage_method (getattr (cls , method )))
2333 return super ().__init__ (name , bases , d )
@@ -48,7 +58,7 @@ class Storage(metaclass=StorageMeta):
4858
4959 # The string used in the config to denote the type of storage. Should be
5060 # overridden by subclasses.
51- storage_name = None
61+ storage_name : str
5262
5363 # The string used in the config to denote a particular instance. Will be
5464 # overridden during instantiation.
@@ -63,7 +73,7 @@ class Storage(metaclass=StorageMeta):
6373 read_only = False
6474
6575 # The attribute values to show in the representation of the storage.
66- _repr_attributes = ()
76+ _repr_attributes : List [ str ] = []
6777
6878 def __init__ (self , instance_name = None , read_only = None , collection = None ):
6979 if read_only is None :
@@ -121,23 +131,23 @@ def __repr__(self):
121131 {x : getattr (self , x ) for x in self ._repr_attributes },
122132 )
123133
124- async def list (self ):
134+ @abstractmethod
135+ async def list (self ) -> List [tuple ]:
125136 """
126137 :returns: list of (href, etag)
127138 """
128- raise NotImplementedError ()
129139
130- async def get (self , href ):
140+ @abstractmethod
141+ async def get (self , href : str ):
131142 """Fetch a single item.
132143
133144 :param href: href to fetch
134145 :returns: (item, etag)
135146 :raises: :exc:`vdirsyncer.exceptions.PreconditionFailed` if item can't
136147 be found.
137148 """
138- raise NotImplementedError ()
139149
140- async def get_multi (self , hrefs ):
150+ async def get_multi (self , hrefs : Iterable [ str ] ):
141151 """Fetch multiple items. Duplicate hrefs must be ignored.
142152
143153 Functionally similar to :py:meth:`get`, but might bring performance
@@ -152,19 +162,16 @@ async def get_multi(self, hrefs):
152162 item , etag = await self .get (href )
153163 yield href , item , etag
154164
155- async def has (self , href ):
156- """Check if an item exists by its href.
157-
158- :returns: True or False
159- """
165+ async def has (self , href ) -> bool :
166+ """Check if an item exists by its href."""
160167 try :
161168 await self .get (href )
162169 except exceptions .PreconditionFailed :
163170 return False
164171 else :
165172 return True
166173
167- async def upload (self , item ):
174+ async def upload (self , item : Item ):
168175 """Upload a new item.
169176
170177 In cases where the new etag cannot be atomically determined (i.e. in
@@ -179,7 +186,7 @@ async def upload(self, item):
179186 """
180187 raise NotImplementedError ()
181188
182- async def update (self , href , item , etag ):
189+ async def update (self , href : str , item : Item , etag ):
183190 """Update an item.
184191
185192 The etag may be none in some cases, see `upload`.
@@ -192,7 +199,7 @@ async def update(self, href, item, etag):
192199 """
193200 raise NotImplementedError ()
194201
195- async def delete (self , href , etag ):
202+ async def delete (self , href : str , etag : str ):
196203 """Delete an item by href.
197204
198205 :raises: :exc:`vdirsyncer.exceptions.PreconditionFailed` when item has
@@ -228,21 +235,19 @@ async def get_meta(self, key: str) -> Optional[str]:
228235 :param key: The metadata key.
229236 :return: The metadata or None, if metadata is missing.
230237 """
231-
232238 raise NotImplementedError ("This storage does not support metadata." )
233239
234240 async def set_meta (self , key : str , value : Optional [str ]):
235- """Get metadata value for collection/storage.
241+ """Set metadata value for collection/storage.
236242
237243 :param key: The metadata key.
238244 :param value: The value. Use None to delete the data.
239245 """
240-
241246 raise NotImplementedError ("This storage does not support metadata." )
242247
243248
244249def normalize_meta_value (value ) -> Optional [str ]:
245250 # `None` is returned by iCloud for empty properties.
246251 if value is None or value == "None" :
247- return
252+ return None
248253 return value .strip () if value else ""
0 commit comments