def test_at_queue_validity(self): valid = atq._validate_queue self.assertRaises(ValueError, atq._validate_queue, 'aa') self.assertRaises(ValueError, atq._validate_queue, '1') self.assertRaises(ValueError, atq._validate_queue, u'\u2022') # This one is a trick question ... "=" is technically a valid queue, # but only in GNU at. It stands for the currently running queue, which # means that in either version it should not be allowed to assign jobs # to it. self.assertRaises(ValueError, atq._validate_queue, '=') self.assertEqual('Q', atq._validate_queue('Q'))
def at(command, when, queue='a'): """ Execute command at when. command may be anything interpreble by /bin/sh. If you need features specific to another shell, create a script and then make the command <path to shell> <path to script>. when may be a datetime.timedelta, a datetime.datetime or a timespec str. If a string is provided, it is assumed that it's a valid timespec. See `timespec` doc in `at`'s documentation. python-atd also has good support for named queues. Both GNU and BSD at support the concept of named queues, which allow you to easily separate different types of jobs based on type. For example, if you owned a bank, you'd have different types of jobs. Check clearing might go in queue "c" 24 hours after request, while international wire clearing would go in queue "i" 48 hours after request. An unfortunate limitation of `at` is that all jobs can only be one letter, A-Z or a-z. This means there are only 52 available queues in both BSD at and GNU at. """ # First build our timespec for `at`... posix_time = False # Since timespecs aren't validated/parsed, we can only easily check datetime check_past = False if isinstance(when, datetime.datetime): timespec = convert_datetime(when) posix_time = True check_past = True elif isinstance(when, datetime.timedelta): timespec = convert_timedelta(when) when = datetime.datetime.now()+when check_past = True elif isinstance(when, str): timespec = when # TODO: Validate timespec? else: raise NotImplementedError('I don\'t support the class you passed to schedule(). Try the builtin datetime.') if check_past: if (when < datetime.datetime.now()): raise ValueError('`when` must be at a time in the future, never in the past') # Build our `at` command line arguments... atargs = list([config.at_binary]) queue = _validate_queue(queue) if posix_time: atargs.append('-t') atargs.extend(timespec.split(" ")) if config.always_send_mail: atargs.append('-m') elif config.never_send_mail: atargs.append('-M') atargs.append('-q') atargs.append(queue) # Prevent creation of a needless subprocess by using a temporary file. # StringIO cannot be used due to Popen's use of fileno(). at_stdin = tempfile.TemporaryFile('w') at_stdin.write(command) at_stdin.seek(0) # Build our Popen keyword arguments... atkwargs = dict(stdin=at_stdin, stdout=PIPE, stderr=PIPE) if not config.inherit_env: atkwargs['env'] = config.atjob_environment sp = Popen(atargs, **atkwargs) (at_stdout, at_stderr) = sp.communicate() at_stdin.close() # Build our AtJob object for user consumption... atjob = AtJob() atjob.from_at_stderr(at_stderr) atjob.command = command atjob.when = when atjob.queue = queue atjob.who = os.getenv("LOGNAME") return atjob