Basically, for each side of a rectangle, you need to check if it is docked to another rectangle in the list, and draw a line segment along that side if it is not docked.
Iterate through all rectangles and create a list of adjacent rectangles:
for i, r in enumerate(rectangles):
neighbours = rectangles[i-1:i] + rectangles[i+1:i+2]
Since the rectangles do not overlap, they do not collide and a collision check fails. Therefore, a rectangle must be modified, it can either be inflated on one side or moved to one side. In my solution, I choose to move the rectangle by 1 to one side and check that the rectangle does not collide with any of the adjacent rectangles. Use pygame.Rect.collidelist for the collision test. This method returns the index of the first rectangle in the list where a collision was detected, otherwise -1. e.g.:
right_test_rect = r.move(1, 0)
if right_test_rect.collidelist(neighbours) < 0:
# [...]
Instead of drawing thick lines with pygame.draw.line I decided to draw rectangles thinly with pygame.draw.rect. pygame.draw.line draws a line between 2 coordinates and inflates the line on both sides. However, I would like to specify the exact rectangular area to be filled.
Minimal example:

import pygame
pygame.init()
window = pygame.display.set_mode((400, 400))
clock = pygame.time.Clock()
def drawOutline(surf, rectangles, color, width):
for r in rectangles:
corners = [
(r.left, r.top), (r.right-width, r.top),
(r.left, r.bottom-width), (r.right-width, r.bottom-width),
]
for c in corners:
pygame.draw.rect(surf, color, (*c, width, width))
for i, r in enumerate(rectangles):
neighbours = rectangles[i-1:i] + rectangles[i+1:i+2]
sides = [
(r.move(-1, 0), (r.left, r.top+width, width, r.height-2*width)),
(r.move( 1, 0), (r.right-width, r.top+width, width, r.height-2*width)),
(r.move( 0, -1), (r.left+width, r.top, r.width-2*width, width)),
(r.move( 0, 1), (r.left+width, r.bottom-width, r.width-2*width, width)),
]
for test_rect, line in sides:
if test_rect.collidelist(neighbours) < 0:
pygame.draw.rect(surf, color, line)
body = [
pygame.Rect(50, 100, 50, 50),
pygame.Rect(100, 100, 50, 50),
pygame.Rect(150, 100, 50, 50),
pygame.Rect(200, 100, 50, 50),
pygame.Rect(200, 150, 50, 50),
pygame.Rect(150, 150, 50, 50),
pygame.Rect(100, 150, 50, 50),
pygame.Rect(100, 200, 50, 50),
pygame.Rect(100, 250, 50, 50),
pygame.Rect(150, 250, 50, 50),
pygame.Rect(200, 250, 50, 50),
pygame.Rect(250, 250, 50, 50),
]
run = True
while run:
clock.tick(100)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
window.fill(0)
drawOutline(window, body, "yellow", 3)
pygame.display.flip()
pygame.quit()
exit()