11import tempfile
22import shutil
3+ import inspect
34
45from io import IOBase
56from functools import cached_property
67from pathlib import Path
78from importlib_resources import files
8- from datetime import datetime
9+ from datetime import datetime , timedelta
910
1011from typing import Protocol , Sequence , Optional , Mapping
1112
@@ -253,7 +254,14 @@ def pin_write(
253254 "but that directory already exists."
254255 )
255256
256- self .fs .put (tmp_dir , dst_version_path , recursive = True )
257+ res = self .fs .put (tmp_dir , dst_version_path , recursive = True )
258+
259+ if dst_version_path == dst_pin_path :
260+ # TODO(refactor): this is a RSConnect specific hack
261+ # since we don't know the bundle id ahead of time, the meta version
262+ # object is incorrect. Could fix through the meta_factory
263+ bundle_version = VersionRaw (res .split ("/" )[- 1 ])
264+ meta .version = bundle_version
257265
258266 return meta
259267
@@ -289,7 +297,7 @@ def pin_upload(self, paths, name=None, title=None, description=None, metadata=No
289297 raise NotImplementedError ()
290298
291299 def pin_version_delete (self , name : str , version : str ):
292- """TODO: Delete a single version of a pin.
300+ """Delete a single version of a pin.
293301
294302 Parameters
295303 ----------
@@ -298,10 +306,16 @@ def pin_version_delete(self, name: str, version: str):
298306 version:
299307 Version identifier.
300308 """
301- raise NotImplementedError ()
302309
303- def pin_versions_prune (self , name , n = None , days = None ):
304- """TODO: Delete old versions of a pin.
310+ pin_name = self .path_to_pin (name )
311+
312+ pin_version_path = self .construct_path ([pin_name , version ])
313+ self .fs .rm (pin_version_path , recursive = True )
314+
315+ def pin_versions_prune (
316+ self , name , n : "int | None" = None , days : "int | None" = None
317+ ):
318+ """Delete old versions of a pin.
305319
306320 Parameters
307321 ----------
@@ -318,7 +332,33 @@ def pin_versions_prune(self, name, n=None, days=None):
318332 the most recent version.
319333
320334 """
321- raise NotImplementedError ()
335+
336+ if n is None and days is None :
337+ raise ValueError ("Cannot specify both n and days." )
338+
339+ versions = self .pin_versions (name , as_df = False )
340+ if n is not None :
341+ if n <= 0 :
342+ raise ValueError ("Argument n is {n}, but must be greater than 0." )
343+
344+ to_delete = versions [:- n ]
345+ if days is not None :
346+ if days <= 0 :
347+ raise ValueError ("Argument days is {days}, but must be greater than 0." )
348+
349+ date_cutoff = datetime .today () - timedelta (days = days )
350+ to_delete = [v for v in versions if v .created < date_cutoff ]
351+
352+ # message user about deletions ----
353+ # TODO(question): how to pin_inform? Log or warning?
354+ if to_delete :
355+ str_vers = ", " .join ([v .version for v in to_delete ])
356+ print (f"Deleting versions: { str_vers } ." )
357+ if not to_delete :
358+ print ("No old versions to delete" )
359+
360+ for version in to_delete :
361+ self .pin_version_delete (name , version .version )
322362
323363 def pin_search (self , search = None ):
324364 """TODO: Search for pins.
@@ -334,15 +374,24 @@ def pin_search(self, search=None):
334374 """
335375 raise NotImplementedError ()
336376
337- def pin_delete (self , names ):
377+ def pin_delete (self , names : "str | Sequence[str]" ):
338378 """TODO: Delete a pin (or pins), removing it from the board.
339379
340380 Parameters
341381 ----------
342382 names:
343383 The names of one or more pins to delete.
344384 """
345- raise NotImplementedError ()
385+
386+ if isinstance (names , str ):
387+ names = [names ]
388+
389+ for name in names :
390+ if not self .pin_exists (name ):
391+ raise PinsError ("Cannot delete pin, since pin %s does not exist" % name )
392+
393+ pin_name = self .path_to_pin (name )
394+ self .fs .rm (pin_name , recursive = True )
346395
347396 def pin_browse (self , name , version = None , local = False ):
348397 """TODO: Navigate to the home of a pin, either on the internet or locally.
@@ -460,6 +509,25 @@ def pin_list(self):
460509 names = [f"{ cont ['owner_username' ]} /{ cont ['name' ]} " for cont in results ]
461510 return names
462511
512+ def pin_version_delete (self , * args , ** kwargs ):
513+ from pins .rsconnect .api import RsConnectApiRequestError
514+
515+ try :
516+ super ().pin_version_delete (* args , ** kwargs )
517+ except RsConnectApiRequestError as e :
518+ if e .args [0 ]["code" ] != 75 :
519+ raise e
520+
521+ raise PinsError ("RStudio Connect cannot delete the latest pin version." )
522+
523+ def pin_versions_prune (self , * args , ** kwargs ):
524+ sig = inspect .signature (super ().pin_versions_prune )
525+ if sig .bind (* args , ** kwargs ).arguments .get ("days" ) is not None :
526+ raise NotImplementedError (
527+ "RStudio Connect board cannot prune versions using days."
528+ )
529+ super ().pin_versions_prune (* args , ** kwargs )
530+
463531 def validate_pin_name (self , name ) -> None :
464532 if name .count ("/" ) > 1 :
465533 raise ValueError (f"Invalid pin name: { name } " )
0 commit comments