33import hashlib
44import hmac
55import json
6- import structlog
76import re
7+ from functools import namedtuple
88
9+ import structlog
910from django .shortcuts import get_object_or_404
1011from rest_framework import permissions , status
1112from rest_framework .exceptions import NotFound , ParseError
1415from rest_framework .status import HTTP_400_BAD_REQUEST
1516from rest_framework .views import APIView
1617
17- from readthedocs .core .signals import (
18- webhook_bitbucket ,
19- webhook_github ,
20- webhook_gitlab ,
21- )
18+ from readthedocs .core .signals import webhook_bitbucket , webhook_github , webhook_gitlab
2219from readthedocs .core .views .hooks import (
2320 build_branches ,
2421 build_external_version ,
2724 trigger_sync_versions ,
2825)
2926from readthedocs .integrations .models import HttpExchange , Integration
30- from readthedocs .projects .models import Feature , Project
27+ from readthedocs .projects .models import Project
3128
3229log = structlog .get_logger (__name__ )
3330
5552BITBUCKET_PUSH = 'repo:push'
5653
5754
55+ ExternalVersionData = namedtuple (
56+ "ExternalVersionData" ,
57+ ["id" , "source_branch" , "base_branch" , "commit" ],
58+ )
59+
60+
5861class WebhookMixin :
5962
6063 """Base class for Webhook mixins."""
@@ -228,20 +231,22 @@ def get_external_version_response(self, project):
228231 :param project: Project instance
229232 :type project: readthedocs.projects.models.Project
230233 """
231- identifier , verbose_name = self .get_external_version_data ()
234+ version_data = self .get_external_version_data ()
232235 # create or get external version object using `verbose_name`.
233236 external_version = get_or_create_external_version (
234- project , identifier , verbose_name
237+ project = project ,
238+ version_data = version_data ,
235239 )
236240 # returns external version verbose_name (pull/merge request number)
237241 to_build = build_external_version (
238- project = project , version = external_version , commit = identifier
242+ project = project ,
243+ version = external_version ,
239244 )
240245
241246 return {
242- ' build_triggered' : True ,
243- ' project' : project .slug ,
244- ' versions' : [to_build ],
247+ " build_triggered" : bool ( to_build ) ,
248+ " project" : project .slug ,
249+ " versions" : [to_build ] if to_build else [ ],
245250 }
246251
247252 def get_deactivated_external_version_response (self , project ):
@@ -259,9 +264,10 @@ def get_deactivated_external_version_response(self, project):
259264 :param project: Project instance
260265 :type project: Project
261266 """
262- identifier , verbose_name = self .get_external_version_data ()
267+ version_data = self .get_external_version_data ()
263268 deactivated_version = deactivate_external_version (
264- project , identifier , verbose_name
269+ project = project ,
270+ version_data = version_data ,
265271 )
266272 return {
267273 'version_deactivated' : bool (deactivated_version ),
@@ -320,13 +326,16 @@ def get_data(self):
320326 def get_external_version_data (self ):
321327 """Get Commit Sha and pull request number from payload."""
322328 try :
323- identifier = self .data ['pull_request' ]['head' ]['sha' ]
324- verbose_name = str (self .data ['number' ])
325-
326- return identifier , verbose_name
327-
328- except KeyError :
329- raise ParseError ('Parameters "sha" and "number" are required' )
329+ data = ExternalVersionData (
330+ id = str (self .data ["number" ]),
331+ commit = self .data ["pull_request" ]["head" ]["sha" ],
332+ source_branch = self .data ["pull_request" ]["head" ]["ref" ],
333+ base_branch = self .data ["pull_request" ]["base" ]["ref" ],
334+ )
335+ return data
336+ except KeyError as e :
337+ key = e .args [0 ]
338+ raise ParseError (f"Invalid payload. { key } is required." )
330339
331340 def is_payload_valid (self ):
332341 """
@@ -523,13 +532,16 @@ def is_payload_valid(self):
523532 def get_external_version_data (self ):
524533 """Get commit SHA and merge request number from payload."""
525534 try :
526- identifier = self .data ['object_attributes' ]['last_commit' ]['id' ]
527- verbose_name = str (self .data ['object_attributes' ]['iid' ])
528-
529- return identifier , verbose_name
530-
531- except KeyError :
532- raise ParseError ('Parameters "id" and "iid" are required' )
535+ data = ExternalVersionData (
536+ id = str (self .data ["object_attributes" ]["iid" ]),
537+ commit = self .data ["object_attributes" ]["last_commit" ]["id" ],
538+ source_branch = self .data ["object_attributes" ]["source_branch" ],
539+ base_branch = self .data ["object_attributes" ]["target_branch" ],
540+ )
541+ return data
542+ except KeyError as e :
543+ key = e .args [0 ]
544+ raise ParseError (f"Invalid payload. { key } is required." )
533545
534546 def handle_webhook (self ):
535547 """
0 commit comments