class_docstr: Copy files and their availability metadata from one dataset to another.
  
  The difference to a system copy command is that here additional content
  availability information, such as registered URLs, is also copied to the
  target dataset. Moreover, potentially required git-annex special remote
  configurations are detected in a source dataset and are applied to a target
  dataset in an analogous fashion. It is possible to copy a file for which no
  content is available locally, by just copying the required metadata on
  content identity and availability.
  
  .. note::
    At the moment, only URLs for the special remotes 'web' (git-annex built-in)
    and 'datalad' are recognized and transferred.
  
  || REFLOW >>
  The interface is modeled after the POSIX 'cp' command, but with one
  additional way to specify what to copy where: [CMD: --specs-from CMD][PY:
  `specs_from` PY] allows the caller to flexibly input source-destination
  path pairs.
  << REFLOW ||
  
  || REFLOW >>
  This command can copy files out of and into a hierarchy of nested datasets.
  Unlike with other DataLad command, the [CMD: --recursive CMD][PY: `recursive`
  PY] switch does not enable recursion into subdatasets, but is analogous
  to the POSIX 'cp' command switch and enables subdirectory recursion, regardless
  of dataset boundaries. It is not necessary to enable recursion in order to
  save changes made to nested target subdatasets.
  << REFLOW ||
code: def __call__(
      path=None,
      *,
      dataset=None,
      recursive=False,
      target_dir=None,
      specs_from=None,
      message=None,
  ):
      # Concept
      #
      # Loosely model after the POSIX cp command
      #
      # 1. Determine the target of the copy operation, and its associated
      #    dataset
      #
      # 2. for each source: determine source dataset, query for metadata, put
      #    into target dataset
      #
      # Instead of sifting and sorting through input args, process them one
      # by one sequentially. Utilize lookup caching to make things faster,
      # instead of making the procedure itself more complicated.
  
      if path and specs_from:
          raise ValueError(
              "Path argument(s) AND a specs-from specified, "
              "this is not supported."
          )
  
      ds = None
      if dataset:
          ds = require_dataset(
              dataset, check_installed=True, purpose="copy into"
          )
  
      if target_dir:
          target_dir = resolve_path(target_dir, dataset)
  
      if path:
          # turn into list of absolute paths
          paths = [resolve_path(p, dataset) for p in ensure_list(path)]
  
          # we already checked that there are no specs_from
          if not target_dir:
              if len(paths) == 1:
                  if not ds:
                      raise ValueError("No target directory was given.")
                  # we can keep target_dir unset and need not manipulate
                  # paths, this is all done in a generic fashion below
              elif len(paths) == 2:
                  # single source+dest combo
                  if paths[-1].is_dir():
                      # check if we need to set target_dir, in case dest
                      # is a dir
                      target_dir = paths.pop(-1)
                  else:
                      specs_from = [paths]
              else:
                  target_dir = paths.pop(-1)
  
          if not specs_from:
              # in all other cases we have a plain source list
              specs_from = paths
  
      if not specs_from:
          raise ValueError("Neither `paths` nor `specs_from` given.")
  
      if target_dir:
          if ".git" in target_dir.parts:
              raise ValueError(
                  "Target directory should not contain a .git directory: {}".format(
                      target_dir
                  )
              )
      elif ds:
          # no specific target set, but we have to write into a dataset,
          # and one was given. It seems to make sense to use this dataset
          # as a target. it is already to reference for any path resolution.
          # Any explicitly given destination, will take precedence over
          # a general target_dir setting nevertheless.
          target_dir = ds.pathobj
  
      res_kwargs = dict(
          action="copy_file",
          logger=lgr,
      )
  
      # lookup cache for dir to repo mappings, and as a DB for cleaning
      # things up
      repo_cache = _StaticRepoCache()
      # which paths to pass on to save
      to_save = []
      try:
          for src_path, dest_path in _yield_specs(specs_from):
              src_path = Path(src_path)
              dest_path = (
                  None
                  if dest_path is None
                  else resolve_path(dest_path, dataset)
              )
              lgr.debug(
                  "Processing copy specification: %s -> %s",
                  src_path,
                  dest_path,
              )
  
              # Some checks, first impossibility "wins"
              msg_impossible = None
              if not recursive and src_path.is_dir():
                  msg_impossible = (
                      "recursion not enabled, omitting directory"
                  )
              elif (
                  dest_path and dest_path.name == ".git"
              ) or src_path.name == ".git":
                  msg_impossible = (
                      "refuse to place '.git' into destination dataset"
                  )
              elif not (dest_path or target_dir):
                  msg_impossible = (
                      "need destination path or target directory"
                  )
  
              if msg_impossible:
                  yield dict(
                      path=str(src_path),
                      status="impossible",
                      message=msg_impossible,
                      **res_kwargs,
                  )
                  continue
  
              for src_file, dest_file in _yield_src_dest_filepaths(
                  src_path, dest_path, target_dir=target_dir
              ):
                  if ds and ds.pathobj not in dest_file.parents:
                      # take time to compose proper error
                      dpath = str(
                          target_dir if target_dir else dest_path
                      )
                      yield dict(
                          path=dpath,
                          status="error",
                          message=(
                              "reference dataset does not contain "
                              "destination path: %s",
                              dpath,
                          ),
                          **res_kwargs,
                      )
                      # only recursion could yield further results, which would
                      # all have the same issue, so call it over right here
                      break
                  for res in _copy_file(
                      src_file, dest_file, cache=repo_cache
                  ):
                      yield dict(res, **res_kwargs)
                      if res.get("status", None) == "ok":
                          to_save.append(res["destination"])
      finally:
          # cleanup time
          repo_cache.clear()
  
      if not (ds and to_save):
          # nothing left to do
          return
  
      yield from ds.save(
          path=to_save,
          # we provide an explicit file list
          recursive=False,
          message=message,
          return_type="generator",
          result_renderer="disabled",
      )
  
imports: import logging
  import os.path
  from shutil import copyfile
  import sys
  from datalad.interface.base import Interface
  from datalad.interface.base import build_doc, eval_results
  from datalad.support.constraints import EnsureStr, EnsureNone
  from datalad.interface.common_opts import save_message_opt
  from datalad.support.param import Parameter
  from datalad.distribution.dataset import Dataset, require_dataset
  from datalad.support.annexrepo import AnnexRepo
  from datalad.support.exceptions import CapturedException
  from datalad.utils import ensure_list, get_dataset_root, Path
  from datalad.distribution.dataset import EnsureDataset, datasetmethod, resolve_path
  from functools import lru_cache
label_desc: Use image node_id 1 for calling the CopyFile obj's underlying member method code with example usage: obj.__call__(path) without return types