<SYSTEM_TASK:>
Solve the following problem using Python, implementing the functions described below, one line at a time
<END_TASK>
<USER_TASK:>
Description:
 def add_dependent_work_units(self, work_unit, depends_on, hard=True):
 """Add work units, where one prevents execution of the other. The two work units may be attached to different work specs, but both must be in this task master's namespace. `work_unit` and `depends_on` are both tuples of (work spec name, work unit name, work unit dictionary). The work specs must already exist; they may be created with :meth:`update_bundle` with an empty work unit dictionary. If a work unit dictionary is provided with either work unit, then this defines that work unit, and any existing definition is replaced. Either or both work unit dictionaries may be :const:`None`, in which case the work unit is not created if it does not already exist. In this last case, the other work unit will be added if specified, but the dependency will not be added, and this function will return :const:`False`. In all other cases, this dependency is added in addition to all existing dependencies on either or both work units, even if the work unit dictionary is replaced. `work_unit` will not be executed or reported as available via :meth:`get_work` until `depends_on` finishes execution. If the `depends_on` task fails, then the `hard` parameter describes what happens: if `hard` is :const:`True` then `work_unit` will also fail, but if `hard` is :const:`False` then `work_unit` will be able to execute even if `depends_on` fails, it just must have completed some execution attempt. Calling this function with ``hard=True`` suggests an ordered sequence of tasks where the later task depends on the output of the earlier tasks. Calling this function with ``hard=False`` suggests a cleanup task that must run after this task (and, likely, several others) are done, but doesn't specifically depend on its result being available. :param work_unit: "Later" work unit to execute :paramtype work_unit: tuple of (str,str,dict) :param depends_on: "Earlier" work unit to execute :paramtype depends_on: tuple of (str,str,dict) :param bool hard: if True, then `work_unit` automatically fails if `depends_on` fails :return: :const:`True`, unless one or both of the work units didn't exist and weren't specified, in which case, :const:`False` :raise rejester.exceptions.NoSuchWorkSpecError: if a work spec was named that doesn't exist """

        # There's no good, not-confusing terminology here.
        # I'll call work_unit "later" and depends_on "earlier"
        # consistently, because that at least makes the time flow
        # correct.
        later_spec, later_unit, later_unitdef = work_unit
        earlier_spec, earlier_unit, earlier_unitdef = depends_on
        with self.registry.lock(identifier=self.worker_id) as session:
            # Bail if either work spec doesn't already exist
            if session.get(WORK_SPECS, later_spec) is None:
                raise NoSuchWorkSpecError(later_spec)
            if session.get(WORK_SPECS, earlier_spec) is None:
                raise NoSuchWorkSpecError(earlier_spec)
            
            # Cause both work units to exist (if possible)
            # Note that if "earlier" is already finished, we may be
            # able to make "later" available immediately
            earlier_done = False
            earlier_successful = False
            if earlier_unitdef is not None:
                session.update(WORK_UNITS_ + earlier_spec,
                               { earlier_unit: earlier_unitdef })
            else:
                earlier_unitdef = session.get(
                    WORK_UNITS_ + earlier_spec, earlier_unit)
                if earlier_unitdef is None:
                    earlier_unitdef = session.get(
                        WORK_UNITS_ + earlier_spec + _BLOCKED, earlier_unit)
                if earlier_unitdef is None:
                    earlier_unitdef = session.get(
                        WORK_UNITS_ + earlier_spec + _FINISHED, earlier_unit)
                    if earlier_unitdef is not None:
                        earlier_done = True
                        earlier_successful = True
                if earlier_unitdef is None:
                    earlier_unitdef = session.get(
                        WORK_UNITS_ + earlier_spec + _FAILED, earlier_unit)
                    if earlier_unitdef is not None:
                        earlier_done = True

            later_failed = earlier_done and hard and not earlier_successful
            later_unblocked = ((earlier_done and not later_failed) or
                               (earlier_unitdef is None))
            if later_failed:
                later_destination = WORK_UNITS_ + later_spec + _FAILED
            elif later_unblocked:
                later_destination = WORK_UNITS_ + later_spec
            else:
                later_destination = WORK_UNITS_ + later_spec + _BLOCKED

            if later_unitdef is not None:
                for suffix in ['', _FINISHED, _FAILED, _BLOCKED]:
                    k = WORK_UNITS_ + later_spec + suffix
                    if k != later_destination:
                        session.popmany(k, later_unit)
                session.update(later_destination,
                               { later_unit: later_unitdef })
            elif earlier_unitdef is not None:
                later_unitdef = session.get(
                    WORK_UNITS_ + later_spec, later_unit)
                if later_unitdef is not None:
                    session.move(
                        WORK_UNITS_ + later_spec,
                        WORK_UNITS_ + later_spec + _BLOCKED,
                        { later_unit: later_unitdef })
                else:
                    later_unitdef = session.get(
                        WORK_UNITS_ + later_spec + _BLOCKED, later_unit)

            if later_unitdef is None or earlier_unitdef is None:
                return False

            # Now both units exist and are in the right place;
            # record the dependency
            blocks = session.get(WORK_UNITS_ + earlier_spec + _BLOCKS,
                                 earlier_unit)
            if blocks is None: blocks = []
            blocks.append([later_spec, later_unit, hard])
            session.set(WORK_UNITS_ + earlier_spec + _BLOCKS,
                        earlier_unit, blocks)

            depends = session.get(WORK_UNITS_ + later_spec + _DEPENDS,
                                  later_unit)
            if depends is None: depends = []
            depends.append([earlier_spec, earlier_unit])
            session.set(WORK_UNITS_ + later_spec + _DEPENDS,
                        later_unit, depends)

            return True