2021-04-14 20:58:06 +00:00
import os
2022-08-05 03:18:39 +00:00
import hashlib
2022-01-04 20:32:28 +00:00
import sys
2023-02-24 08:40:21 +00:00
import glob
2021-04-14 20:58:06 +00:00
import re
import tempfile
import shutil
import requests
import mimetypes
import mistune
2022-01-04 20:32:28 +00:00
import contextlib
2023-02-24 08:40:21 +00:00
import time
2023-02-24 16:30:07 +00:00
import logging
logger = logging . getLogger ( ' mkdocs ' )
2023-02-24 08:40:21 +00:00
2021-12-07 04:26:10 +00:00
from time import sleep
2021-04-14 20:58:06 +00:00
from mkdocs . config import config_options
from mkdocs . plugins import BasePlugin
from md2cf . confluence_renderer import ConfluenceRenderer
2021-06-13 06:27:08 +00:00
from os import environ
2022-10-14 10:40:16 +00:00
from pathlib import Path
2021-04-14 20:58:06 +00:00
2023-02-24 08:40:21 +00:00
ENABLE_ENV_VAR = " MKDOCS_TO_CONFLUENCE "
DRY_RUN_ENV_VAR = " MKDOCS_TO_CONFLUENCE_DRY_RUN "
2021-04-14 20:58:06 +00:00
TEMPLATE_BODY = " <p> TEMPLATE </p> "
2023-02-24 16:30:07 +00:00
HEADER_WARNING = " ‼️ This page is created automatically, all you changes will be overwritten during the next MKDocs deployment. Do not edit a page here ‼️ "
SECTION_PAGE_CONTENT = " <p> It ' s just a Section Page </p> "
2021-04-28 20:03:06 +00:00
2023-02-24 08:40:21 +00:00
# -- I don't know why it's here
2022-01-04 20:32:28 +00:00
@contextlib.contextmanager
def nostdout ( ) :
save_stdout = sys . stdout
sys . stdout = DummyFile ( )
yield
sys . stdout = save_stdout
2023-02-24 08:40:21 +00:00
# -- I don't know why it's here
2022-01-04 20:32:28 +00:00
class DummyFile ( object ) :
def write ( self , x ) :
pass
2021-04-14 20:58:06 +00:00
class MkdocsWithConfluence ( BasePlugin ) :
config_scheme = (
2021-04-28 20:03:06 +00:00
( " host_url " , config_options . Type ( str , default = None ) ) ,
( " space " , config_options . Type ( str , default = None ) ) ,
( " parent_page_name " , config_options . Type ( str , default = None ) ) ,
2021-06-22 04:54:27 +00:00
( " username " , config_options . Type ( str , default = environ . get ( " JIRA_USERNAME " , None ) ) ) ,
( " password " , config_options . Type ( str , default = environ . get ( " JIRA_PASSWORD " , None ) ) ) ,
2021-04-28 20:03:06 +00:00
( " dryrun " , config_options . Type ( bool , default = False ) ) ,
2023-02-24 16:30:07 +00:00
( " header_message " , config_options . Type ( str , default = None ) ) ,
( " upstream_url " , config_options . Type ( str , default = None ) ) ,
( " header_warning " , config_options . Type ( str , default = HEADER_WARNING ) ) ,
2021-04-14 20:58:06 +00:00
)
def __init__ ( self ) :
2023-02-24 08:40:21 +00:00
self . enabled = False
2021-04-14 20:58:06 +00:00
self . confluence_renderer = ConfluenceRenderer ( use_xhtml = True )
self . confluence_mistune = mistune . Markdown ( renderer = self . confluence_renderer )
2021-04-28 20:03:06 +00:00
self . simple_log = False
2021-04-14 20:58:06 +00:00
self . flen = 1
2022-10-14 10:14:34 +00:00
self . session = requests . Session ( )
2022-10-14 10:40:16 +00:00
self . page_attachments = { }
2023-02-24 08:40:21 +00:00
self . repo_url = None
2023-02-24 16:30:07 +00:00
self . header_message = None
self . upstream_url = None
2021-04-14 20:58:06 +00:00
2023-02-24 08:40:21 +00:00
def on_config ( self , config ) :
# ------------------------------------------------------
# -- Enable the plugin by setting environment variable
# ------------------------------------------------------
if os . environ . get ( ENABLE_ENV_VAR ) :
logger . info ( " MKDocs with Confluence is enabled " )
self . enabled = True
else :
logger . info (
f " MKDocs with Confluence is disabled, set the { ENABLE_ENV_VAR } to enable the plugin "
)
# ------------------------------------------------------
# -- Set the dry-run mode
# ------------------------------------------------------
if self . config [ " dryrun " ] or os . environ . get ( DRY_RUN_ENV_VAR ) :
logger . info ( " dry-run mode is turned on, your changes won ' t be synced with Confluence " )
self . dryrun = True
else :
logger . info ( " dry-run mode is turned off, your changes will be synced with Confluence " )
self . dryrun = False
# ------------------------------------------------------
# -- Set git url to add to a confluence page
# ------------------------------------------------------
if config [ " repo_url " ] :
self . repo_url = config [ " repo_url " ]
logger . info ( f " git url is set to { self . repo_url } " )
2023-02-24 16:30:07 +00:00
# ------------------------------------------------------
# -- Set a custom header to add to a confluence page
# ------------------------------------------------------
if self . config [ " header_message " ] :
self . header_message = self . config [ " header_message " ]
logger . info ( f " header message is set to { self . header_message } " )
# ------------------------------------------------------
# -- Set an upstream url to add to a confluence page
# ------------------------------------------------------
if self . config [ " upstream_url " ] :
self . upstream_url = self . config [ " upstream_url " ]
logger . info ( f " upstream url is set to { self . upstream_url } " )
2021-04-28 20:03:06 +00:00
2021-04-14 20:58:06 +00:00
def on_files ( self , files , config ) :
2021-04-28 20:03:06 +00:00
pages = files . documentation_pages ( )
2021-12-07 04:26:10 +00:00
try :
self . flen = len ( pages )
2023-02-24 08:40:21 +00:00
logger . debug ( f " number of Files in directory tree: { self . flen } " )
2021-12-07 04:26:10 +00:00
except 0 :
2023-02-24 08:40:21 +00:00
logger . error ( " no files found to be synced " )
2021-04-14 20:58:06 +00:00
def on_page_markdown ( self , markdown , page , config , files ) :
2023-02-24 08:40:21 +00:00
# TODO: Modify pages here
try :
2023-02-24 16:30:07 +00:00
self . session . auth = ( self . config [ " username " ] , self . config [ " password " ] )
confluencePageName = page . url [ 0 : - 1 ]
#.replace("/", "-")
if self . config [ " parent_page_name " ] is not None :
parent_page = self . config [ " parent_page_name " ]
else :
parent_page = self . config [ " space " ]
page_name = " "
# TODO: Refactor
if confluencePageName . rsplit ( ' / ' , 1 ) [ 0 ] :
confluencePageName = ( f " { confluencePageName . rsplit ( ' / ' , 1 ) [ 0 ] } + { page . title . replace ( ' ' , ' ' ) } " )
else :
confluencePageName = ( f " { page . title . replace ( ' ' , ' ' ) } " )
# Create empty pages for sections only
logger . info ( " preparing emtpy pages for sections " )
for path in page . url . rsplit ( " / " , 2 ) [ 0 ] . split ( " / " ) :
logger . debug ( f " path is { path } " )
parent_id = self . find_page_id ( parent_page )
if path :
if page_name :
page_name = page_name + " " + path
else :
page_name = path
logger . info ( f " Will create a page { page_name } under the { parent_page } " )
self . add_page ( page_name , parent_id , SECTION_PAGE_CONTENT )
parent_page = page_name
parent_id = self . find_page_id ( parent_page )
confluencePageName = parent_page + " " + page . title
new_markdown = markdown
# -- Adding an upstream url
if self . upstream_url :
new_markdown = f " >Original page is here: { self . upstream_url } / { page . url } \n \n " + new_markdown
# -- Adding a header message
if self . header_message :
new_markdown = f " > { self . header_message } \n \n " + new_markdown
# -- Adding a repo url
if self . repo_url :
new_markdown = f " >You can edit documentation here: { self . repo_url } \n \n " + new_markdown
# -- Adding a header warning
new_markdown = f " > { self . config [ ' header_warning ' ] } \n \n " + new_markdown
# -------------------------------------------------
# -- Sync attachments
# -------------------------------------------------
attachments = [ ]
# -- TODO: support named picture
md_image_reg = " (?:[!] \ [(?P<caption>.*?) \ ]) \ ((?P<image>.*?) \ )(?P<options> \ { .* \ })? "
try :
for match in re . finditer ( md_image_reg , markdown ) :
# -- TODO: I'm sure it can be done better
attachment_path = " ./docs " + match . group ( 2 )
logger . info ( f " found image: ./docs { match . group ( 2 ) } " )
images = re . search ( md_image_reg , new_markdown )
# -- TODO: Options maybe the reason why page is invalid, but I'm not sure about it yet
# new_markdown = new_markdown.replace(images.group("options"), "")
new_markdown = re . sub ( md_image_reg , f " <p><ac:image><ri:attachment ri:filename= \" { os . path . basename ( attachment_path ) } \" /></ac:image></p> " , new_markdown )
attachments . append ( attachment_path )
except AttributeError as e :
logger . warning ( e )
logger . debug ( f " attachments: { attachments } " )
confluence_body = self . confluence_mistune ( new_markdown )
self . add_page ( confluencePageName , parent_id , confluence_body )
if attachments :
logger . debug ( f " UPLOADING ATTACHMENTS TO CONFLUENCE FOR { page . title } , DETAILS: " )
logger . debug ( f " FILES: { attachments } " )
for attachment in attachments :
logger . debug ( f " trying to upload { attachment } to { confluencePageName } " )
if self . enabled :
try :
self . add_or_update_attachment ( confluencePageName , attachment )
except Exception as Argument :
logger . warning ( Argument )
except Exception as exp :
logger . error ( exp )
2021-04-14 20:58:06 +00:00
return markdown
2022-10-14 10:40:16 +00:00
def on_post_page ( self , output , page , config ) :
2023-02-24 08:40:21 +00:00
logger . info ( " The author was uploading images here, maybe there was a reason for that " )
2022-10-14 10:40:16 +00:00
2021-04-14 20:58:06 +00:00
def on_page_content ( self , html , page , config , files ) :
return html
2021-12-07 04:26:10 +00:00
def __get_page_url ( self , section ) :
return re . search ( " url= ' (.*) ' \\ ) " , section ) . group ( 1 ) [ : - 1 ] + " .md "
def __get_page_name ( self , section ) :
return os . path . basename ( re . search ( " url= ' (.*) ' \\ ) " , section ) . group ( 1 ) [ : - 1 ] )
2022-01-04 20:32:28 +00:00
def __get_section_name ( self , section ) :
2023-02-24 08:40:21 +00:00
logger . debug ( f " SECTION name: { section } " )
2022-01-04 20:32:28 +00:00
return os . path . basename ( re . search ( " url= ' (.*) ' \\ / " , section ) . group ( 1 ) [ : - 1 ] )
2021-04-14 20:58:06 +00:00
def __get_section_title ( self , section ) :
2023-02-24 08:40:21 +00:00
logger . debug ( f " SECTION title: { section } " )
2022-01-04 20:32:28 +00:00
try :
r = re . search ( " Section \\ (title= ' (.*) ' \\ ) " , section )
return r . group ( 1 )
except AttributeError :
name = self . __get_section_name ( section )
2023-02-24 08:40:21 +00:00
logger . warning ( f " Section ' { name } ' doesn ' t exist in the mkdocs.yml nav section! " )
2022-01-04 20:32:28 +00:00
return name
2021-04-28 20:03:06 +00:00
def __get_page_title ( self , section ) :
2021-12-07 04:26:10 +00:00
try :
2022-01-04 20:32:28 +00:00
r = re . search ( " \\ s*Page \\ (title= ' (.*) ' , " , section )
2021-12-07 04:26:10 +00:00
return r . group ( 1 )
except AttributeError :
name = self . __get_page_url ( section )
2023-02-24 08:40:21 +00:00
logger . warning ( f " Page ' { name } ' doesn ' t exist in the mkdocs.yml nav section! " )
2022-01-04 20:32:28 +00:00
return name
2021-04-14 20:58:06 +00:00
2022-08-05 03:18:39 +00:00
# Adapted from https://stackoverflow.com/a/3431838
def get_file_sha1 ( self , file_path ) :
hash_sha1 = hashlib . sha1 ( )
with open ( file_path , " rb " ) as f :
for chunk in iter ( lambda : f . read ( 4096 ) , b " " ) :
hash_sha1 . update ( chunk )
return hash_sha1 . hexdigest ( )
def add_or_update_attachment ( self , page_name , filepath ) :
2023-02-24 08:40:21 +00:00
logger . warning ( f " Mkdocs With Confluence * { page_name } *ADD/Update ATTACHMENT if required* { filepath } " )
logger . debug ( f " Mkdocs With Confluence: Add Attachment: PAGE NAME: { page_name } , FILE: { filepath } " )
2021-04-14 20:58:06 +00:00
page_id = self . find_page_id ( page_name )
if page_id :
2022-08-05 03:18:39 +00:00
file_hash = self . get_file_sha1 ( filepath )
attachment_message = f " MKDocsWithConfluence [v { file_hash } ] "
existing_attachment = self . get_attachment ( page_id , filepath )
if existing_attachment :
file_hash_regex = re . compile ( r " \ [v([a-f0-9] {40} )]$ " )
existing_match = file_hash_regex . search ( existing_attachment [ " version " ] [ " message " ] )
if existing_match is not None and existing_match . group ( 1 ) == file_hash :
2023-02-24 08:40:21 +00:00
logger . debug ( f " * Mkdocs With Confluence * { page_name } * Existing attachment skipping * { filepath } " )
2021-04-14 20:58:06 +00:00
else :
2022-08-05 03:18:39 +00:00
self . update_attachment ( page_id , filepath , existing_attachment , attachment_message )
else :
self . create_attachment ( page_id , filepath , attachment_message )
2021-04-14 20:58:06 +00:00
else :
2023-02-24 08:40:21 +00:00
logger . debug ( " PAGE DOES NOT EXISTS " )
2021-04-28 20:03:06 +00:00
2022-08-05 03:18:39 +00:00
def get_attachment ( self , page_id , filepath ) :
name = os . path . basename ( filepath )
2023-02-24 08:40:21 +00:00
logger . debug ( f " * Mkdocs With Confluence: Get Attachment: PAGE ID: { page_id } , FILE: { filepath } " )
2022-08-05 03:18:39 +00:00
url = self . config [ " host_url " ] + " / " + page_id + " /child/attachment "
headers = { " X-Atlassian-Token " : " no-check " } # no content-type here!
2023-02-24 08:40:21 +00:00
logger . debug ( f " URL: { url } " )
2022-08-05 03:18:39 +00:00
2022-10-14 10:14:34 +00:00
r = self . session . get ( url , headers = headers , params = { " filename " : name , " expand " : " version " } )
2022-08-05 03:18:39 +00:00
r . raise_for_status ( )
with nostdout ( ) :
response_json = r . json ( )
if response_json [ " size " ] :
return response_json [ " results " ] [ 0 ]
def update_attachment ( self , page_id , filepath , existing_attachment , message ) :
2023-02-24 08:40:21 +00:00
logger . debug ( f " * Mkdocs With Confluence: Update Attachment: PAGE ID: { page_id } , FILE: { filepath } " )
2022-08-05 03:18:39 +00:00
url = self . config [ " host_url " ] + " / " + page_id + " /child/attachment/ " + existing_attachment [ " id " ] + " /data "
headers = { " X-Atlassian-Token " : " no-check " } # no content-type here!
2023-02-24 08:40:21 +00:00
logger . debug ( f " URL: { url } " )
2022-10-14 10:40:16 +00:00
filename = os . path . basename ( filepath )
2022-08-05 03:18:39 +00:00
# determine content-type
2022-10-14 10:40:16 +00:00
content_type , encoding = mimetypes . guess_type ( filepath )
2022-08-05 03:18:39 +00:00
if content_type is None :
content_type = " multipart/form-data "
2022-10-14 10:40:16 +00:00
files = { " file " : ( filename , open ( Path ( filepath ) , " rb " ) , content_type ) , " comment " : message }
2022-08-05 03:18:39 +00:00
if not self . dryrun :
2022-10-14 10:14:34 +00:00
r = self . session . post ( url , headers = headers , files = files )
2022-08-05 03:18:39 +00:00
r . raise_for_status ( )
2023-02-24 08:40:21 +00:00
logger . debug ( r . json ( ) )
2022-08-05 03:18:39 +00:00
if r . status_code == 200 :
2023-02-24 08:40:21 +00:00
logger . info ( " OK! " )
2022-08-05 03:18:39 +00:00
else :
print ( " ERR! " )
def create_attachment ( self , page_id , filepath , message ) :
2023-02-24 08:40:21 +00:00
logger . debug ( f " * Mkdocs With Confluence: Create Attachment: PAGE ID: { page_id } , FILE: { filepath } " )
2022-08-05 03:18:39 +00:00
url = self . config [ " host_url " ] + " / " + page_id + " /child/attachment "
headers = { " X-Atlassian-Token " : " no-check " } # no content-type here!
2023-02-24 08:40:21 +00:00
logger . debug ( f " URL: { url } " )
2022-10-14 10:40:16 +00:00
filename = os . path . basename ( filepath )
2022-08-05 03:18:39 +00:00
# determine content-type
2022-10-14 10:40:16 +00:00
content_type , encoding = mimetypes . guess_type ( filepath )
2022-08-05 03:18:39 +00:00
if content_type is None :
content_type = " multipart/form-data "
2022-10-14 10:40:16 +00:00
files = { " file " : ( filename , open ( filepath , " rb " ) , content_type ) , " comment " : message }
2022-08-05 03:18:39 +00:00
if not self . dryrun :
2022-10-14 10:14:34 +00:00
r = self . session . post ( url , headers = headers , files = files )
2023-02-24 08:40:21 +00:00
logger . debug ( r . json ( ) )
2022-08-05 03:18:39 +00:00
r . raise_for_status ( )
if r . status_code == 200 :
2023-02-24 08:40:21 +00:00
logger . debug ( " OK! " )
2022-08-05 03:18:39 +00:00
else :
2023-02-24 08:40:21 +00:00
logger . debug ( " ERR! " )
2022-08-05 03:18:39 +00:00
2021-04-14 20:58:06 +00:00
def find_page_id ( self , page_name ) :
2023-02-24 08:40:21 +00:00
logger . info ( f " looking for a page id of the page: { page_name } " )
2021-04-14 20:58:06 +00:00
name_confl = page_name . replace ( " " , " + " )
2021-04-28 20:03:06 +00:00
url = self . config [ " host_url " ] + " ?title= " + name_confl + " &spaceKey= " + self . config [ " space " ] + " &expand=history "
2023-02-24 08:40:21 +00:00
logger . debug ( f " URL: { url } " )
2022-10-14 10:14:34 +00:00
r = self . session . get ( url )
2021-04-14 20:58:06 +00:00
r . raise_for_status ( )
2022-01-04 20:32:28 +00:00
with nostdout ( ) :
response_json = r . json ( )
2021-04-14 20:58:06 +00:00
if response_json [ " results " ] :
2023-02-24 08:40:21 +00:00
logger . debug ( f " response: { response_json } " )
2021-04-28 20:03:06 +00:00
return response_json [ " results " ] [ 0 ] [ " id " ]
2021-04-14 20:58:06 +00:00
else :
2023-02-24 08:40:21 +00:00
logger . debug ( f " page { page_name } doens ' t exist " )
2021-04-14 20:58:06 +00:00
return None
2021-04-28 20:03:06 +00:00
2021-04-14 20:58:06 +00:00
def add_page ( self , page_name , parent_page_id , page_content_in_storage_format ) :
2023-02-24 08:40:21 +00:00
logger . info ( f " Creating a new page: { page_name } under page with ID: { parent_page_id } " )
if self . enabled :
if self . find_page_id ( page_name ) :
self . update_page ( page_name , page_content_in_storage_format )
2021-04-14 20:58:06 +00:00
else :
2023-02-24 08:40:21 +00:00
logger . info ( f " Creating a new page: { page_name } under page with ID: { parent_page_id } " )
url = self . config [ " host_url " ] + " / "
logger . debug ( f " URL: { url } " )
headers = { " Content-Type " : " application/json " }
space = self . config [ " space " ]
data = {
" type " : " page " ,
" title " : page_name ,
" space " : { " key " : space } ,
" ancestors " : [ { " id " : parent_page_id } ] ,
" body " : { " storage " : { " value " : page_content_in_storage_format , " representation " : " storage " } } ,
}
logger . debug ( f " DATA: { data } " )
if not self . dryrun :
try :
r = self . session . post ( url , json = data , headers = headers )
r . raise_for_status ( )
except Exception as exp :
logger . error ( exp )
if r . status_code == 200 :
logger . info ( f " page created: { page_name } " )
else :
logger . error ( f " page can ' t be created: { page_name } " )
2021-04-28 20:03:06 +00:00
2021-04-14 20:58:06 +00:00
def update_page ( self , page_name , page_content_in_storage_format ) :
page_id = self . find_page_id ( page_name )
2023-02-24 08:40:21 +00:00
logger . debug ( f " updating page { page_name } " )
2021-04-14 20:58:06 +00:00
if page_id :
page_version = self . find_page_version ( page_name )
page_version = page_version + 1
2021-04-28 20:03:06 +00:00
url = self . config [ " host_url " ] + " / " + page_id
headers = { " Content-Type " : " application/json " }
2021-05-05 04:28:07 +00:00
space = self . config [ " space " ]
2021-04-14 20:58:06 +00:00
data = {
2021-04-28 20:03:06 +00:00
" id " : page_id ,
" title " : page_name ,
" type " : " page " ,
2021-05-05 04:28:07 +00:00
" space " : { " key " : space } ,
2021-04-28 20:03:06 +00:00
" body " : { " storage " : { " value " : page_content_in_storage_format , " representation " : " storage " } } ,
" version " : { " number " : page_version } ,
}
2021-04-14 20:58:06 +00:00
if not self . dryrun :
2023-02-24 08:40:21 +00:00
try :
r = self . session . put ( url , json = data , headers = headers )
r . raise_for_status ( )
except Exception as exp :
logger . error ( exp )
2021-04-14 20:58:06 +00:00
if r . status_code == 200 :
2023-02-24 08:40:21 +00:00
logger . info ( f " page created: { page_name } " )
2021-04-14 20:58:06 +00:00
else :
2023-02-24 08:40:21 +00:00
logger . error ( f " page can ' t be created: { page_name } " )
2021-04-14 20:58:06 +00:00
else :
2023-02-24 08:40:21 +00:00
logger . warning ( " page {page_name} doesn ' t exist " )
2021-04-28 20:03:06 +00:00
2021-04-14 20:58:06 +00:00
def find_page_version ( self , page_name ) :
2023-02-24 08:40:21 +00:00
logger . debug ( f " INFO - * Mkdocs With Confluence: Find PAGE VERSION, PAGE NAME: { page_name } " )
2021-04-14 20:58:06 +00:00
name_confl = page_name . replace ( " " , " + " )
2021-04-28 20:03:06 +00:00
url = self . config [ " host_url " ] + " ?title= " + name_confl + " &spaceKey= " + self . config [ " space " ] + " &expand=version "
2022-10-14 10:14:34 +00:00
r = self . session . get ( url )
2021-04-14 20:58:06 +00:00
r . raise_for_status ( )
2022-01-04 20:32:28 +00:00
with nostdout ( ) :
response_json = r . json ( )
if response_json [ " results " ] is not None :
2023-02-24 08:40:21 +00:00
logger . debug ( f " VERSION: { response_json [ ' results ' ] [ 0 ] [ ' version ' ] [ ' number ' ] } " )
2021-04-28 20:03:06 +00:00
return response_json [ " results " ] [ 0 ] [ " version " ] [ " number " ]
2021-04-14 20:58:06 +00:00
else :
2023-02-24 08:40:21 +00:00
logger . debug ( " PAGE DOES NOT EXISTS " )
2021-04-14 20:58:06 +00:00
return None
def find_parent_name_of_page ( self , name ) :
2023-02-24 08:40:21 +00:00
logger . debug ( f " INFO - * Mkdocs With Confluence: Find PARENT OF PAGE, PAGE NAME: { name } " )
2021-04-14 20:58:06 +00:00
idp = self . find_page_id ( name )
2021-04-28 20:03:06 +00:00
url = self . config [ " host_url " ] + " / " + idp + " ?expand=ancestors "
2022-10-14 10:14:34 +00:00
r = self . session . get ( url )
2021-04-14 20:58:06 +00:00
r . raise_for_status ( )
2022-01-04 20:32:28 +00:00
with nostdout ( ) :
response_json = r . json ( )
2021-04-14 20:58:06 +00:00
if response_json :
2023-02-24 08:40:21 +00:00
logger . debug ( f " PARENT NAME: { response_json [ ' ancestors ' ] [ - 1 ] [ ' title ' ] } " )
2021-05-05 04:28:07 +00:00
return response_json [ " ancestors " ] [ - 1 ] [ " title " ]
2021-04-14 20:58:06 +00:00
else :
2023-02-24 08:40:21 +00:00
logger . debug ( " PAGE DOES NOT HAVE PARENT " )
2021-04-14 20:58:06 +00:00
return None
2021-04-28 20:03:06 +00:00
def wait_until ( self , condition , interval = 0.1 , timeout = 1 ) :
start = time . time ( )
while not condition and time . time ( ) - start < timeout :
time . sleep ( interval )
2023-02-24 08:40:21 +00:00