Building a graph using dictionary
To many accumulators, paths and graph, you just need one.
One for loop to many, you can do everything with one loop.
open with no close is a no no. Sure you could aruge that the codebase is small and you will just run the function once and then terminate... But it's good practice to always close your file handlers. Instead of using open() and close() you shuold just use with open(filename, 'r') as f:, because with clause take care of close() for you.
Route is not use anywhere else so no need for it.
You are doing del paths[-1] twice so you will actually remove one vaild path.
The for loop is stupidly complex for such a simple problem. I dont even know where to start in my quest of understading it.


graph[node][path[0]] doesn't do what you say it does; it makes a dict of dict, and not a list of tuples in a dict as you specified. eg. the code returns {birmingham : {'manchester': 84, 'bristol': 85}} instead of {birmingham : [('manchester', 84), ('bristol', 85)]}.
So theres a lot of if else statements in the loop to solve the problem of: "what to do when the dict/list isn't instantiated" . This can be done in a simpler way with dict.setdefault(), this function basically checks if the dict has an list (dict or whatever you want) as a child, and if it dosen't it will instantiate it for you.

I dont think that's a graph because not everything in that dataset is connected to eachother, its more like a set of graphs.




def new_sol(filename):
    graph = {}
    with open(filename, 'r') as f:
        for line in f:
            city1, city2, distance = line.lower().split()
            if distance == "input":
                break
            #what you say you want
            graph.setdefault(city1, []).append((city2,int(distance)))
            graph.setdefault(city2, []).append((city1,int(distance)))
            #same as your code
            #graph.setdefault(city1, {})[city2] = int(distance)
            #graph.setdefault(city2, {})[city1] = int(distance)
    return graph
if __name__ == "__main__":
    print(new_sol("test.txt"))




Time analysis

You wanted to optimize the code, so lets us do an time analysis to see if there is some thing we can optimize in our algorithms, or if the optimaztion is just in the implementaion.

askers code


read line: \$O(N)\$
put line in arr: \$O(1)\$
iterate over paths: \$O(N)\$
put the diffrent directions in a dict \$O(2)\$


\$O(N*1+N*2) = O(N*3) = O(N)\$

proposed code


read line: \$O(N)\$
put line in arr/dict: \$O(2*(1+1))\$, \$(1+1)\$ comes from lookup/insertion + insertion


\$O(N*4) = O(N)\$

So from a algorithmic standpoint both your code and my code preforme equally well, and that is the fastst we can do it.
From an implentaion standpoint my code is a bit faster but as \$N\$ goes toward infinity it dosent really matter.