 (use-package importmagic
   :delight)
 
+(defun python-black-setup ()
+  (let ((requirements (python-project-requirements)))
+    (when (cond ((and (python-use-pre-commit-p requirements)
+                      (python-pre-commit-config-has-hook-p "black"))
+
+                 (make-local-variable 'python-black-command)
+                 (setf python-black-command
+                       (concat (python-pre-commit-virtualenv-path "black") "/bin/black"))
+
+                 (make-local-variable 'python-black-d-command)
+                 (let ((blackd-path (concat (python-pre-commit-virtualenv-path "black") "/bin/blackd")))
+                   (setf python-black-d-command
+                         (and (condition-case err
+                                  (zerop (call-process blackd-path nil nil nil "--version"))
+                                (error (message "%s" (error-message-string err)) nil))
+                              blackd-path))))
+
+                ((and (python-use-poetry-p)
+                      (member "black" requirements))
+
+                 ;; Set `python-black-command'
+                 (make-local-variable 'python-black-command)
+                 (setf python-black-command "poetry")
+                 (make-local-variable 'python-black--base-args)
+                 (setf python-black--base-args (append '("run" "black") python-black--base-args))
+
+                 ;; Set `python-black-d-command'
+                 (make-local-variable 'python-black-d-command)
+                 (setf python-black-d-command
+                       (with-temp-buffer
+                         (if (condition-case err
+                                 (and (call-process "poetry" nil t nil "run" "which" python-blackd-command)
+                                      (zerop (call-process "poetry" nil nil nil "run"
+                                                           python-black-d-command "--version")))
+                               (error (message "%s" (error-message-string err)) nil))
+                             (string-trim (buffer-string))
+                           nil))))
+
+                ((or (executable-find python-black-command)
+                     (and (executable-find python-black-d-command)
+                          (zerop (call-process python-black-d-command nil nil nil "--version"))))
+                 t)
+                (t
+                 (make-local-variable 'python-black-command)
+                 (make-local-variable 'python-black-d-command)
+                 (setf python-black-command nil
+                       python-black-d-command nil)))
+
+      ;; Set `blackd' request headers
+      (when-let (python-black-d-command
+                 (pyproject-path (python-pyproject-path))
+                 (black-d-request-headers
+                  (let-alist (python-parse-pyproject)
+                    `(,@(if .tool.black.line-length
+                            `(,(cons "X-Line-Length" (format "%s" .tool.black.line-length))))
+                      ,@(if (and .tool.black.skip-string-normalization
+                                 (not (eq .tool.black.skip.string-normalization :false)))
+                            `(,(cons "X-Skip-String-Normalization" "true")))
+                      ,@(if (or .tool.black.fast .tool.black.safe)
+                            `(,(cons "X-Fast-Or-Safe" (if .tool.black.fast "fast" "safe"))))
+                      ,@(if (or .tool.black.pyi .tool.black.target-version)
+                            `(,(cons "X-Python-Variant"
+                                     (if .tool.black.pyi
+                                         "pyi"
+                                       (funcall 'string-join
+                                                (append .tool.black.target-version) ",")))))))))
+        (make-local-variable 'python-black-d-request-headers-function)
+        (setf python-black-d-request-headers-function (lambda () black-d-request-headers)))
+
+      (python-black-on-save-mode 1))))
+
+(defun python-isort-setup ()
+  (let ((requirements (python-project-requirements)))
+    (when (cond ((and (python-use-pre-commit-p requirements)
+                      (python-pre-commit-config-has-hook-p "isort"))
+                 (make-local-variable 'python-isort-command)
+                 (setf python-isort-command
+                       (concat (python-pre-commit-virtualenv-path "isort") "/bin/isort")))
+                ((and (python-use-poetry-p)
+                      (member "isort" requirements))
+                 (make-local-variable 'python-isort-command)
+                 (make-local-variable 'python-isort-arguments)
+                 (setf python-isort-command "poetry"
+                       python-isort-arguments
+                       (append '("run" "isort") python-isort-arguments)))
+                ((executable-find python-isort-command)
+                 (when (and (boundp 'python-black-command)
+                            python-black-command
+                            (executable-find python-black-command))
+                   (make-local-variable 'python-isort-arguments)
+                   (setf python-isort-arguments
+                         (append '("--profile" "black") python-isort-arguments)))
+                 t))
+      (python-isort-on-save-mode 1))))
+
 (add-hook 'python-mode-hook
           (lambda ()
             ;; Set `python-shell-interpreter'

                        (condition-case err
                            (process-lines "pip" "list" "--format=freeze" "--disable-pip-version-check")
                          (error (message "%s" (error-message-string err)) nil)))))
      (assoc-delete-all file-path python-project-requirements-cache)
      (when requirements
        (push (cons file-path requirements) python-project-requirements-cache)))
    requirements))

(add-hook 'kill-buffer-hook
          (lambda ()
            (setf python-project-requirements-cache
                  (assoc-delete-all
                   (buffer-file-name)
                   python-project-requirements-cache))))

(defun python-pre-commit-config-has-hook-p (id)
  (member id
          (flatten-list
           (cl-loop for repo in (alist-get 'repos (python-pre-commit-config))
                    collect (cl-loop for hook in (alist-get 'hooks repo)
                                     collect (alist-get 'id hook))))))

(defvar python-pre-commit-database-cache nil)
(defun python-pre-commit-virtualenv-path (hook-id)
  (use-package emacsql-sqlite :ensure t)
  (when-let* ((db-file (concat
                        (file-name-as-directory
                         (or (getenv "PRE_COMMIT_HOME")
                             (getenv "XDG_CACHE_HOME")
                             "~/.cache/"))
                        "pre-commit/db.db"))
              (db (and (file-exists-p db-file)
                       (let ((db-file-mod-time
                              (file-attribute-modification-time (file-attributes db-file))))
                         (or (and (equal
                                   (cdr python-pre-commit-database-cache)
                                   db-file-mod-time)
                                  (car python-pre-commit-database-cache))
                             (when-let ((conn (emacsql-sqlite db-file :debug t)))
                               (unwind-protect
                                   (progn
                                     (setf python-pre-commit-database-cache
                                           (cons
                                            (emacsql conn [:select * :from repos])
                                            db-file-mod-time))
                                     (car python-pre-commit-database-cache))
                                 (emacsql-close conn)))))))
              (repo-config
               (seq-find
                (lambda (repo)
                  (seq-find
                   (lambda (hook)
                     (equal (alist-get 'id hook) hook-id))
                   (alist-get 'hooks repo)))
                (alist-get 'repos (python-pre-commit-config)))))

    (let* ((additional-deps
            (alist-get 'additional_dependencies
                       (seq-find
                        (lambda (hook)
                          (equal (alist-get 'id hook) hook-id))
                        (alist-get 'hooks repo-config))))
           (repo-url
            (concat (alist-get 'repo repo-config)
                    (if additional-deps
                        (concat ":" (string-join additional-deps ":")))))
           (repo-dir (symbol-name
                      (nth 2 (seq-find
                              (lambda (row)
                                (and (equal (symbol-name (car row))
                                            repo-url)
                                     (equal (symbol-name (cadr row))
                                            (alist-get 'rev repo-config))))
                              db)))))
      (car
       (last
        (file-expand-wildcards
         (concat (file-name-as-directory repo-dir) "py_env-*")
         t))))))

(use-package flycheck
  :delight

(defvar python-pyproject-cache nil)
(defun python-parse-pyproject ()
  (let* ((pyproject-path (python-pyproject-path))
         (cache-value (assoc-default pyproject-path python-pyproject-cache)))
    (if cache-value
        cache-value
      (python-watch-config-file pyproject-path 'python-pyproject-cache 'python-parse-config-file)
      (when-let ((content (python-parse-config-file pyproject-path)))
        (push `(,pyproject-path . ,content) python-pyproject-cache)
        content))))

(defun python-use-pre-commit-p (requirements)
