Packaging python scripts and dependencies

I’m trying to understand the right idiom for packaging some python scripts with their dependencies. I always get a bit lost creating a derivation for software that isn’t “built”. I want my python code, dependencies, and run shell script ex. uvicorn backend:app all in a derivation such that nix run starts the web server.

        packages.${system}.default = pkgs.stdenv.mkDerivation {
          pname = "webapp";
          version = "0.1.0";
          src = ./src;
    
          buildInputs = with pkgs; [
            python3
            python3Packages.fastapi
            python3Packages.uvicorn        
            python3Packages.httpx
          ];
    
          installPhase = ''
            mkdir -p $out/bin
            cp *.py $out/bin/
    
            cat > $out/bin/webapp << EOF
            #!${pkgs.bash}/bin/bash
            cd $out/bin        
            exec ${pkgs.python3}/bin/python -m uvicorn backend:app
            EOF
            chmod +x $out/bin/webapp
          '';
    
          meta.mainProgram = "webapp";
        };

This snippet works, but is this the right way to package my program? Should I use propagatedBuildInputs ? I’d also prefer to have the run script not in nix but rather ./src/run.sh and I’m not sure how to make the dependencies available to it.