/ scripts / add_attribute.py
add_attribute.py
  1  #!/usr/bin/env python
  2  ############################ Copyrights and license ############################
  3  #                                                                              #
  4  # Copyright 2013 Vincent Jacques <vincent@vincent-jacques.net>                 #
  5  # Copyright 2014 Thialfihar <thi@thialfihar.org>                               #
  6  # Copyright 2014 Vincent Jacques <vincent@vincent-jacques.net>                 #
  7  # Copyright 2016 Peter Buckley <dx-pbuckley@users.noreply.github.com>          #
  8  # Copyright 2018 Yossarian King <yggy@blackbirdinteractive.com>                #
  9  # Copyright 2018 sfdye <tsfdye@gmail.com>                                      #
 10  # Copyright 2019 Steve Kowalik <steven@wedontsleep.org>                        #
 11  # Copyright 2019 Wan Liuyang <tsfdye@gmail.com>                                #
 12  # Copyright 2020 Isac Souza <isouza@daitan.com>                                #
 13  # Copyright 2020 Steve Kowalik <steven@wedontsleep.org>                        #
 14  # Copyright 2020 Wan Liuyang <tsfdye@gmail.com>                                #
 15  # Copyright 2021 karsten-wagner <39054096+karsten-wagner@users.noreply.github.com>#
 16  # Copyright 2022 Gabriele Oliaro <ict@gabrieleoliaro.it>                       #
 17  # Copyright 2023 Jonathan Leitschuh <Jonathan.Leitschuh@gmail.com>             #
 18  #                                                                              #
 19  # This file is part of PyGithub.                                               #
 20  # http://pygithub.readthedocs.io/                                              #
 21  #                                                                              #
 22  # PyGithub is free software: you can redistribute it and/or modify it under    #
 23  # the terms of the GNU Lesser General Public License as published by the Free  #
 24  # Software Foundation, either version 3 of the License, or (at your option)    #
 25  # any later version.                                                           #
 26  #                                                                              #
 27  # PyGithub is distributed in the hope that it will be useful, but WITHOUT ANY  #
 28  # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS    #
 29  # FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more #
 30  # details.                                                                     #
 31  #                                                                              #
 32  # You should have received a copy of the GNU Lesser General Public License     #
 33  # along with PyGithub. If not, see <http://www.gnu.org/licenses/>.             #
 34  #                                                                              #
 35  ################################################################################
 36  from __future__ import annotations
 37  
 38  import os.path
 39  import sys
 40  
 41  className, attributeName, attributeType = sys.argv[1:4]
 42  if len(sys.argv) > 4:
 43      attributeClassType = sys.argv[4]
 44  else:
 45      attributeClassType = ""
 46  
 47  types = {
 48      "string": (
 49          "str",
 50          None,
 51          'self._makeStringAttribute(attributes["' + attributeName + '"])',
 52          "str",
 53      ),
 54      "int": (
 55          "int",
 56          None,
 57          'self._makeIntAttribute(attributes["' + attributeName + '"])',
 58          "int",
 59      ),
 60      "bool": (
 61          "bool",
 62          None,
 63          'self._makeBoolAttribute(attributes["' + attributeName + '"])',
 64          "bool",
 65      ),
 66      "datetime": (
 67          "datetime",
 68          "str",
 69          'self._makeDatetimeAttribute(attributes["' + attributeName + '"])',
 70          "datetime",
 71      ),
 72      "class": (
 73          ":class:`" + attributeClassType + "`",
 74          None,
 75          "self._makeClassAttribute(" + attributeClassType + ', attributes["' + attributeName + '"])',
 76          attributeClassType,
 77      ),
 78  }
 79  
 80  attributeDocType, attributeAssertType, attributeValue, attributeClassType = types[attributeType]
 81  if attributeType == "class":
 82      # Wrap in quotes to avoid an explicit import requirement which can cause circular import errors
 83      attributeClassType = f"'{attributeClassType}'"
 84  
 85  fileName = os.path.join("github", className + ".py")
 86  
 87  
 88  def add_as_class_property(lines: list[str]) -> list[str]:
 89      newLines = []
 90      i = 0
 91  
 92      added = False
 93  
 94      isCompletable = True
 95      isProperty = False
 96      while not added:
 97          line = lines[i].rstrip()
 98          i += 1
 99          if line.startswith("class "):
100              if "NonCompletableGithubObject" in line:
101                  isCompletable = False
102          elif line == "    @property":
103              isProperty = True
104          elif line.startswith("    def "):
105              attrName = line[8:-7]
106              # Properties will be inserted after __repr__, but before any other function.
107              if (not attrName.startswith("__repr__") and not attrName.startswith("_initAttributes")) and (
108                  attrName == "_identity" or attrName > attributeName or not isProperty
109              ):
110                  if not isProperty:
111                      newLines.append("    @property")
112                  newLines.append("    def " + attributeName + "(self) -> " + attributeClassType + ":")
113                  if isCompletable:
114                      newLines.append("        self._completeIfNotSet(self._" + attributeName + ")")
115                  newLines.append("        return self._" + attributeName + ".value")
116                  newLines.append("")
117                  if isProperty:
118                      newLines.append("    @property")
119                  added = True
120              isProperty = False
121          newLines.append(line)
122  
123      while i < len(lines):
124          line = lines[i].rstrip()
125          i += 1
126          newLines.append(line)
127  
128      return newLines
129  
130  
131  def add_to_initAttributes(lines: list[str]) -> list[str]:
132      newLines = []
133      added = False
134  
135      i = 0
136      inInit = False
137  
138      while not added:
139          line = lines[i].rstrip()
140          i += 1
141          if line.strip().startswith("def _initAttributes(self)"):
142              inInit = True
143          if inInit:
144              if not line or line.endswith(" = github.GithubObject.NotSet") or line.endswith(" = NotSet"):
145                  if line:
146                      attrName = line[14:-29]
147                  if not line or attrName > attributeName:
148                      newLines.append(f"        self._{attributeName}: Attribute[{attributeClassType}] = NotSet")
149                      added = True
150          newLines.append(line)
151  
152      while i < len(lines):
153          line = lines[i].rstrip()
154          i += 1
155          newLines.append(line)
156  
157      return newLines
158  
159  
160  def add_to_useAttributes(lines: list[str]) -> list[str]:
161      i = 0
162      newLines = []
163      added = False
164      inUse = False
165      while not added:
166          try:
167              line = lines[i].rstrip()
168          except IndexError:
169              line = ""
170          i += 1
171          if line.strip().startswith("def _useAttributes(self, attributes:"):
172              inUse = True
173          if inUse:
174              if not line or line.endswith(" in attributes:  # pragma no branch"):
175                  if line:
176                      attrName = line[12:-36]
177                  if not line or attrName > attributeName:
178                      newLines.append('        if "' + attributeName + '" in attributes:  # pragma no branch')
179                      if attributeAssertType:
180                          newLines.append(
181                              '            assert attributes["'
182                              + attributeName
183                              + '"] is None or isinstance(attributes["'
184                              + attributeName
185                              + '"], '
186                              + attributeAssertType
187                              + '), attributes["'
188                              + attributeName
189                              + '"]'
190                          )
191                      newLines.append(f"            self._{attributeName} = {attributeValue}")
192                      added = True
193          newLines.append(line)
194  
195      while i < len(lines):
196          line = lines[i].rstrip()
197          i += 1
198          newLines.append(line)
199  
200      return newLines
201  
202  
203  with open(fileName) as f:
204      source = f.readlines()
205  
206  source = add_as_class_property(source)
207  source = add_to_initAttributes(source)
208  source = add_to_useAttributes(source)
209  
210  with open(fileName, "w", newline="\n") as f:
211      f.write("\n".join(source) + "\n")