def upload_project(self, name, path, archive_name=None, callback=None):
    """Upload project archive.

    :param name: Project name.
    :param path: Local path to zip archive.
    :param archive_name: Filename used for the archive uploaded to Azkaban.
      Defaults to `basename(path)`.
    :param callback: Callback forwarded to the streaming upload.

    """
    self._logger.debug('Uploading archive %r to project %s.', path, name)
    if not exists(path):
      raise AzkabanError('Unable to find archive at %r.' % (path, ))
    if not self.is_valid():
      self._refresh() # ensure that the ID is valid
    archive_name = archive_name or basename(path)
    form = MultipartForm(
      files=[{
        'path': path,
        'name': archive_name,
        'type': 'application/zip' # force this (tempfiles don't have extension)
      }],
      params={
        'ajax': 'upload',
        'project': name,
        'session.id': self.id,
      },
      callback=callback
    )
    # note that we have made sure the ID is valid, for two reasons:
    # + to avoid reuploading large files
    # + to simplify the custom ID update process (form parameter)
    res = _extract_json(self._request(
      method='POST',
      endpoint='manager',
      include_session=False,
      headers=form.headers,
      data=form,
    ))
    self._logger.info(
      'Archive %s for project %s uploaded as %s.', path, name, archive_name
    )
    return res
dataset/ETHPy150Open mtth/azkaban/azkaban/remote.py/Session.upload_project/original