| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent fd40fce commit 29879d6
13 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -368,3 +368,29 @@ | |||
| 368 | 368 | # board lists delete | |
| 369 | 369 | b_list.delete() | |
| 370 | 370 | # end board lists delete | |
| 371 | + | ||
| 372 | + # project file upload by path | ||
| 373 | + # Or provide a full path to the uploaded file | ||
| 374 | + project.upload("filename.txt", filepath="/some/path/filename.txt") | ||
| 375 | + # end project file upload by path | ||
| 376 | + | ||
| 377 | + # project file upload with data | ||
| 378 | + # Upload a file using its filename and filedata | ||
| 379 | + project.upload("filename.txt", filedata="Raw data") | ||
| 380 | + # end project file upload with data | ||
| 381 | + | ||
| 382 | + # project file upload markdown | ||
| 383 | + uploaded_file = project.upload_file("filename.txt", filedata="data") | ||
| 384 | + issue = project.issues.get(issue_id) | ||
| 385 | + issue.notes.create({ | ||
| 386 | + "body": "See the attached file: {}".format(uploaded_file["markdown"]) | ||
| 387 | + }) | ||
| 388 | + # project file upload markdown | ||
| 389 | + | ||
| 390 | + # project file upload markdown custom | ||
| 391 | + uploaded_file = project.upload_file("filename.txt", filedata="data") | ||
| 392 | + issue = project.issues.get(issue_id) | ||
| 393 | + issue.notes.create({ | ||
| 394 | + "body": "See the [attached file]({})".format(uploaded_file["url"]) | ||
| 395 | + }) | ||
| 396 | + # project file upload markdown | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -779,3 +779,51 @@ Delete a list: | |||
| 779 | 779 | .. literalinclude:: projects.py | |
| 780 | 780 | :start-after: # board lists delete | |
| 781 | 781 | :end-before: # end board lists delete | |
| 782 | + | ||
| 783 | + | ||
| 784 | + File Uploads | ||
| 785 | + ============ | ||
| 786 | + | ||
| 787 | + Reference | ||
| 788 | + --------- | ||
| 789 | + | ||
| 790 | + * v4 API: | ||
| 791 | + | ||
| 792 | + + :attr:`gitlab.v4.objects.Project.upload` | ||
| 793 | + + :class:`gitlab.v4.objects.ProjectUpload` | ||
| 794 | + | ||
| 795 | + * v3 API: | ||
| 796 | + | ||
| 797 | + + :attr:`gitlab.v3.objects.Project.upload` | ||
| 798 | + + :class:`gitlab.v3.objects.ProjectUpload` | ||
| 799 | + | ||
| 800 | + * Gitlab API: https://docs.gitlab.com/ce/api/projects.html#upload-a-file | ||
| 801 | + | ||
| 802 | + Examples | ||
| 803 | + -------- | ||
| 804 | + | ||
| 805 | + Upload a file into a project using a filesystem path: | ||
| 806 | + | ||
| 807 | + .. literalinclude:: projects.py | ||
| 808 | + :start-after: # project file upload by path | ||
| 809 | + :end-before: # end project file upload by path | ||
| 810 | + | ||
| 811 | + Upload a file into a project without a filesystem path: | ||
| 812 | + | ||
| 813 | + .. literalinclude:: projects.py | ||
| 814 | + :start-after: # project file upload with data | ||
| 815 | + :end-before: # end project file upload with data | ||
| 816 | + | ||
| 817 | + Upload a file and comment on an issue using the uploaded file's | ||
| 818 | + markdown: | ||
| 819 | + | ||
| 820 | + .. literalinclude:: projects.py | ||
| 821 | + :start-after: # project file upload markdown | ||
| 822 | + :end-before: # end project file upload markdown | ||
| 823 | + | ||
| 824 | + Upload a file and comment on an issue while using custom | ||
| 825 | + markdown to reference the uploaded file: | ||
| 826 | + | ||
| 827 | + .. literalinclude:: projects.py | ||
| 828 | + :start-after: # project file upload markdown custom | ||
| 829 | + :end-before: # end project file upload markdown custom | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -396,11 +396,13 @@ def _raw_list(self, path_, cls, **kwargs): | |||
| 396 | 396 | ||
| 397 | 397 | return results | |
| 398 | 398 | ||
| 399 | - def _raw_post(self, path_, data=None, content_type=None, **kwargs): | ||
| 399 | + def _raw_post(self, path_, data=None, content_type=None, | ||
| 400 | + files=None, **kwargs): | ||
| 400 | 401 | url = '%s%s' % (self._url, path_) | |
| 401 | 402 | opts = self._get_session_opts(content_type) | |
| 402 | 403 | try: | |
| 403 | - return self.session.post(url, params=kwargs, data=data, **opts) | ||
| 404 | + return self.session.post(url, params=kwargs, data=data, | ||
| 405 | + files=files, **opts) | ||
| 404 | 406 | except Exception as e: | |
| 405 | 407 | raise GitlabConnectionError( | |
| 406 | 408 | "Can't connect to GitLab server (%s)" % e) | |
@@ -628,7 +630,7 @@ def _build_url(self, path): | |||
| 628 | 630 | return '%s%s' % (self._url, path) | |
| 629 | 631 | ||
| 630 | 632 | def http_request(self, verb, path, query_data={}, post_data={}, | |
| 631 | - streamed=False, **kwargs): | ||
| 633 | + streamed=False, files=None, **kwargs): | ||
| 632 | 634 | """Make an HTTP request to the Gitlab server. | |
| 633 | 635 | ||
| 634 | 636 | Args: | |
@@ -658,6 +660,11 @@ def sanitized_url(url): | |||
| 658 | 660 | params = query_data.copy() | |
| 659 | 661 | params.update(kwargs) | |
| 660 | 662 | opts = self._get_session_opts(content_type='application/json') | |
| 663 | + | ||
| 664 | + # don't set the content-type header when uploading files | ||
| 665 | + if files is not None: | ||
| 666 | + del opts["headers"]["Content-type"] | ||
| 667 | + | ||
| 661 | 668 | verify = opts.pop('verify') | |
| 662 | 669 | timeout = opts.pop('timeout') | |
| 663 | 670 | ||
@@ -668,7 +675,7 @@ def sanitized_url(url): | |||
| 668 | 675 | # always agree with this decision (this is the case with a default | |
| 669 | 676 | # gitlab installation) | |
| 670 | 677 | req = requests.Request(verb, url, json=post_data, params=params, | |
| 671 | - **opts) | ||
| 678 | + files=files, **opts) | ||
| 672 | 679 | prepped = self.session.prepare_request(req) | |
| 673 | 680 | prepped.url = sanitized_url(prepped.url) | |
| 674 | 681 | result = self.session.send(prepped, stream=streamed, verify=verify, | |
@@ -756,7 +763,8 @@ def http_list(self, path, query_data={}, as_list=None, **kwargs): | |||
| 756 | 763 | # No pagination, generator requested | |
| 757 | 764 | return GitlabList(self, url, query_data, **kwargs) | |
| 758 | 765 | ||
| 759 | - def http_post(self, path, query_data={}, post_data={}, **kwargs): | ||
| 766 | + def http_post(self, path, query_data={}, post_data={}, files=None, | ||
| 767 | + **kwargs): | ||
| 760 | 768 | """Make a POST request to the Gitlab server. | |
| 761 | 769 | ||
| 762 | 770 | Args: | |
@@ -776,7 +784,7 @@ def http_post(self, path, query_data={}, post_data={}, **kwargs): | |||
| 776 | 784 | GitlabParsingError: If the json data could not be parsed | |
| 777 | 785 | """ | |
| 778 | 786 | result = self.http_request('post', path, query_data=query_data, | |
| 779 | - post_data=post_data, **kwargs) | ||
| 787 | + post_data=post_data, files=files, **kwargs) | ||
| 780 | 788 | try: | |
| 781 | 789 | if result.headers.get('Content-Type', None) == 'application/json': | |
| 782 | 790 | return result.json() | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -536,7 +536,7 @@ def __ne__(self, other): | |||
| 536 | 536 | class RESTObject(object): | |
| 537 | 537 | """Represents an object built from server data. | |
| 538 | 538 | ||
| 539 | - It holds the attributes know from te server, and the updated attributes in | ||
| 539 | + It holds the attributes know from the server, and the updated attributes in | ||
| 540 | 540 | another. This allows smart updates, if the object allows it. | |
| 541 | 541 | ||
| 542 | 542 | You can redefine ``_id_attr`` in child classes to specify which attribute | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -173,6 +173,14 @@ class GitlabTimeTrackingError(GitlabOperationError): | |||
| 173 | 173 | pass | |
| 174 | 174 | ||
| 175 | 175 | ||
| 176 | + class GitlabUploadError(GitlabOperationError): | ||
| 177 | + pass | ||
| 178 | + | ||
| 179 | + | ||
| 180 | + class GitlabAttachFileError(GitlabOperationError): | ||
| 181 | + pass | ||
| 182 | + | ||
| 183 | + | ||
| 176 | 184 | class GitlabCherryPickError(GitlabOperationError): | |
| 177 | 185 | pass | |
| 178 | 186 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -68,7 +68,8 @@ | |||
| 68 | 68 | 'unstar': {'required': ['id']}, | |
| 69 | 69 | 'archive': {'required': ['id']}, | |
| 70 | 70 | 'unarchive': {'required': ['id']}, | |
| 71 | - 'share': {'required': ['id', 'group-id', 'group-access']}}, | ||
| 71 | + 'share': {'required': ['id', 'group-id', 'group-access']}, | ||
| 72 | + 'upload': {'required': ['id', 'filename', 'filepath']}}, | ||
| 72 | 73 | gitlab.v3.objects.User: { | |
| 73 | 74 | 'block': {'required': ['id']}, | |
| 74 | 75 | 'unblock': {'required': ['id']}, | |
@@ -348,6 +349,20 @@ def do_user_getbyusername(self, cls, gl, what, args): | |||
| 348 | 349 | except Exception as e: | |
| 349 | 350 | cli.die("Impossible to get user %s" % args['query'], e) | |
| 350 | 351 | ||
| 352 | + def do_project_upload(self, cls, gl, what, args): | ||
| 353 | + try: | ||
| 354 | + project = gl.projects.get(args["id"]) | ||
| 355 | + except Exception as e: | ||
| 356 | + cli.die("Could not load project '{!r}'".format(args["id"]), e) | ||
| 357 | + | ||
| 358 | + try: | ||
| 359 | + res = project.upload(filename=args["filename"], | ||
| 360 | + filepath=args["filepath"]) | ||
| 361 | + except Exception as e: | ||
| 362 | + cli.die("Could not upload file into project", e) | ||
| 363 | + | ||
| 364 | + return res | ||
| 365 | + | ||
| 351 | 366 | ||
| 352 | 367 | def _populate_sub_parser_by_class(cls, sub_parser): | |
| 353 | 368 | for action_name in ['list', 'get', 'create', 'update', 'delete']: | |
@@ -469,6 +484,7 @@ def run(gl, what, action, args, verbose, *fargs, **kwargs): | |||
| 469 | 484 | cli.die("Unknown object: %s" % what) | |
| 470 | 485 | ||
| 471 | 486 | g_cli = GitlabCLI() | |
| 487 | + | ||
| 472 | 488 | method = None | |
| 473 | 489 | what = what.replace('-', '_') | |
| 474 | 490 | action = action.lower().replace('-', '') | |
@@ -491,6 +507,9 @@ def run(gl, what, action, args, verbose, *fargs, **kwargs): | |||
| 491 | 507 | print("") | |
| 492 | 508 | else: | |
| 493 | 509 | print(o) | |
| 510 | + elif isinstance(ret_val, dict): | ||
| 511 | + for k, v in six.iteritems(ret_val): | ||
| 512 | + print("{} = {}".format(k, v)) | ||
| 494 | 513 | elif isinstance(ret_val, gitlab.base.GitlabObject): | |
| 495 | 514 | ret_val.display(verbose) | |
| 496 | 515 | elif isinstance(ret_val, six.string_types): | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -909,6 +909,10 @@ class ProjectIssueNote(GitlabObject): | |||
| 909 | 909 | requiredCreateAttrs = ['body'] | |
| 910 | 910 | optionalCreateAttrs = ['created_at'] | |
| 911 | 911 | ||
| 912 | + # file attachment settings (see #56) | ||
| 913 | + description_attr = "body" | ||
| 914 | + project_id_attr = "project_id" | ||
| 915 | + | ||
| 912 | 916 | ||
| 913 | 917 | class ProjectIssueNoteManager(BaseManager): | |
| 914 | 918 | obj_cls = ProjectIssueNote | |
@@ -933,6 +937,10 @@ class ProjectIssue(GitlabObject): | |||
| 933 | 937 | [('project_id', 'project_id'), ('issue_id', 'id')]), | |
| 934 | 938 | ) | |
| 935 | 939 | ||
| 940 | + # file attachment settings (see #56) | ||
| 941 | + description_attr = "description" | ||
| 942 | + project_id_attr = "project_id" | ||
| 943 | + | ||
| 936 | 944 | def subscribe(self, **kwargs): | |
| 937 | 945 | """Subscribe to an issue. | |
| 938 | 946 | ||
@@ -1057,6 +1065,7 @@ class ProjectIssueManager(BaseManager): | |||
| 1057 | 1065 | ||
| 1058 | 1066 | class ProjectMember(GitlabObject): | |
| 1059 | 1067 | _url = '/projects/%(project_id)s/members' | |
| 1068 | + | ||
| 1060 | 1069 | requiredUrlAttrs = ['project_id'] | |
| 1061 | 1070 | requiredCreateAttrs = ['access_level', 'user_id'] | |
| 1062 | 1071 | optionalCreateAttrs = ['expires_at'] | |
@@ -2096,6 +2105,60 @@ def trigger_build(self, ref, token, variables={}, **kwargs): | |||
| 2096 | 2105 | r = self.gitlab._raw_post(url, data=data, **kwargs) | |
| 2097 | 2106 | raise_error_from_response(r, GitlabCreateError, 201) | |
| 2098 | 2107 | ||
| 2108 | + # see #56 - add file attachment features | ||
| 2109 | + def upload(self, filename, filedata=None, filepath=None, **kwargs): | ||
| 2110 | + """Upload the specified file into the project. | ||
| 2111 | + | ||
| 2112 | + .. note:: | ||
| 2113 | + | ||
| 2114 | + Either ``filedata`` or ``filepath`` *MUST* be specified. | ||
| 2115 | + | ||
| 2116 | + Args: | ||
| 2117 | + filename (str): The name of the file being uploaded | ||
| 2118 | + filedata (bytes): The raw data of the file being uploaded | ||
| 2119 | + filepath (str): The path to a local file to upload (optional) | ||
| 2120 | + | ||
| 2121 | + Raises: | ||
| 2122 | + GitlabConnectionError: If the server cannot be reached | ||
| 2123 | + GitlabUploadError: If the file upload fails | ||
| 2124 | + GitlabUploadError: If ``filedata`` and ``filepath`` are not | ||
| 2125 | + specified | ||
| 2126 | + GitlabUploadError: If both ``filedata`` and ``filepath`` are | ||
| 2127 | + specified | ||
| 2128 | + | ||
| 2129 | + Returns: | ||
| 2130 | + dict: A ``dict`` with the keys: | ||
| 2131 | + * ``alt`` - The alternate text for the upload | ||
| 2132 | + * ``url`` - The direct url to the uploaded file | ||
| 2133 | + * ``markdown`` - Markdown for the uploaded file | ||
| 2134 | + """ | ||
| 2135 | + if filepath is None and filedata is None: | ||
| 2136 | + raise GitlabUploadError("No file contents or path specified") | ||
| 2137 | + | ||
| 2138 | + if filedata is not None and filepath is not None: | ||
| 2139 | + raise GitlabUploadError("File contents and file path specified") | ||
| 2140 | + | ||
| 2141 | + if filepath is not None: | ||
| 2142 | + with open(filepath, "rb") as f: | ||
| 2143 | + filedata = f.read() | ||
| 2144 | + | ||
| 2145 | + url = ("/projects/%(id)s/uploads" % { | ||
| 2146 | + "id": self.id, | ||
| 2147 | + }) | ||
| 2148 | + r = self.gitlab._raw_post( | ||
| 2149 | + url, | ||
| 2150 | + files={"file": (filename, filedata)}, | ||
| 2151 | + ) | ||
| 2152 | + # returns 201 status code (created) | ||
| 2153 | + raise_error_from_response(r, GitlabUploadError, expected_code=201) | ||
| 2154 | + data = r.json() | ||
| 2155 | + | ||
| 2156 | + return { | ||
| 2157 | + "alt": data['alt'], | ||
| 2158 | + "url": data['url'], | ||
| 2159 | + "markdown": data['markdown'] | ||
| 2160 | + } | ||
| 2161 | + | ||
| 2099 | 2162 | ||
| 2100 | 2163 | class Runner(GitlabObject): | |
| 2101 | 2164 | _url = '/runners' | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -324,6 +324,8 @@ def get_dict(obj): | |||
| 324 | 324 | else: | |
| 325 | 325 | print(obj) | |
| 326 | 326 | print('') | |
| 327 | + elif isinstance(ret_val, dict): | ||
| 328 | + printer.display(ret_val, verbose=verbose, obj=ret_val) | ||
| 327 | 329 | elif isinstance(ret_val, gitlab.base.RESTObject): | |
| 328 | 330 | printer.display(get_dict(ret_val), verbose=verbose, obj=ret_val) | |
| 329 | 331 | elif isinstance(ret_val, six.string_types): | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -2071,6 +2071,59 @@ def trigger_pipeline(self, ref, token, variables={}, **kwargs): | |||
| 2071 | 2071 | post_data.update(form) | |
| 2072 | 2072 | self.manager.gitlab.http_post(path, post_data=post_data, **kwargs) | |
| 2073 | 2073 | ||
| 2074 | + # see #56 - add file attachment features | ||
| 2075 | + @cli.register_custom_action('Project', ('filename', 'filepath')) | ||
| 2076 | + @exc.on_http_error(exc.GitlabUploadError) | ||
| 2077 | + def upload(self, filename, filedata=None, filepath=None, **kwargs): | ||
| 2078 | + """Upload the specified file into the project. | ||
| 2079 | + | ||
| 2080 | + .. note:: | ||
| 2081 | + | ||
| 2082 | + Either ``filedata`` or ``filepath`` *MUST* be specified. | ||
| 2083 | + | ||
| 2084 | + Args: | ||
| 2085 | + filename (str): The name of the file being uploaded | ||
| 2086 | + filedata (bytes): The raw data of the file being uploaded | ||
| 2087 | + filepath (str): The path to a local file to upload (optional) | ||
| 2088 | + | ||
| 2089 | + Raises: | ||
| 2090 | + GitlabConnectionError: If the server cannot be reached | ||
| 2091 | + GitlabUploadError: If the file upload fails | ||
| 2092 | + GitlabUploadError: If ``filedata`` and ``filepath`` are not | ||
| 2093 | + specified | ||
| 2094 | + GitlabUploadError: If both ``filedata`` and ``filepath`` are | ||
| 2095 | + specified | ||
| 2096 | + | ||
| 2097 | + Returns: | ||
| 2098 | + dict: A ``dict`` with the keys: | ||
| 2099 | + * ``alt`` - The alternate text for the upload | ||
| 2100 | + * ``url`` - The direct url to the uploaded file | ||
| 2101 | + * ``markdown`` - Markdown for the uploaded file | ||
| 2102 | + """ | ||
| 2103 | + if filepath is None and filedata is None: | ||
| 2104 | + raise GitlabUploadError("No file contents or path specified") | ||
| 2105 | + | ||
| 2106 | + if filedata is not None and filepath is not None: | ||
| 2107 | + raise GitlabUploadError("File contents and file path specified") | ||
| 2108 | + | ||
| 2109 | + if filepath is not None: | ||
| 2110 | + with open(filepath, "rb") as f: | ||
| 2111 | + filedata = f.read() | ||
| 2112 | + | ||
| 2113 | + url = ('/projects/%(id)s/uploads' % { | ||
| 2114 | + 'id': self.id, | ||
| 2115 | + }) | ||
| 2116 | + file_info = { | ||
| 2117 | + 'file': (filename, filedata), | ||
| 2118 | + } | ||
| 2119 | + data = self.manager.gitlab.http_post(url, files=file_info) | ||
| 2120 | + | ||
| 2121 | + return { | ||
| 2122 | + "alt": data['alt'], | ||
| 2123 | + "url": data['url'], | ||
| 2124 | + "markdown": data['markdown'] | ||
| 2125 | + } | ||
| 2126 | + | ||
| 2074 | 2127 | ||
| 2075 | 2128 | class Runner(SaveMixin, ObjectDeleteMixin, RESTObject): | |
| 2076 | 2129 | pass | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -98,6 +98,10 @@ testcase "branch deletion" ' | |||
| 98 | 98 | --name branch1 >/dev/null 2>&1 | |
| 99 | 99 | ' | |
| 100 | 100 | ||
| 101 | + testcase "project upload" ' | ||
| 102 | + GITLAB project upload --id "$PROJECT_ID" --filename '$(basename $0)' --filepath '$0' | ||
| 103 | + ' | ||
| 104 | + | ||
| 101 | 105 | testcase "project deletion" ' | |
| 102 | 106 | GITLAB project delete --id "$PROJECT_ID" | |
| 103 | 107 | ' | |
| Back | FazBrowse Home | New Git URL |
0 commit comments