class_docstr: Waits for a file or directory to be present on FTP.
  
  :param path: Remote file or directory path
  :param fail_on_transient_errors: Fail on all errors,
      including 4xx transient errors. Default True.
  :param ftp_conn_id: The :ref:`ftp connection id <howto/connection:ftp>`
      reference to run the sensor against.
code: def poke(self, context: Context) -> bool:
      with self._create_hook() as hook:
          self.log.info("Poking for %s", self.path)
          try:
              mod_time = hook.get_mod_time(self.path)
              self.log.info(
                  "Found File %s last modified: %s", self.path, mod_time
              )
  
          except ftplib.error_perm as e:
              self.log.error("Ftp error encountered: %s", e)
              error_code = self._get_error_code(e)
              if (error_code != 550) and (
                  self.fail_on_transient_errors
                  or (error_code not in self.transient_errors)
              ):
                  if self.soft_fail:
                      raise AirflowSkipException from e
                  raise e
  
              return False
  
          return True
  
file_path: airflow/airflow/providers/ftp/sensors/ftp.py
imports: from __future__ import annotations
  import ftplib
  import re
  from typing import TYPE_CHECKING, Sequence
  from airflow.exceptions import AirflowSkipException
  from airflow.providers.ftp.hooks.ftp import FTPHook, FTPSHook
  from airflow.sensors.base import BaseSensorOperator
label_desc: Use image node_id 4 for calling the FTPSensor obj's underlying member method code with example usage: obj.poke(context) and returns: True, False