cecilia-uu
commited on
Commit
·
ddcaad4
1
Parent(s):
9f0a01c
API Documentation (#1526)
Browse files### What problem does this PR solve?
Adds the doc for the newly added API method.
### Type of change
- [x] Documentation Update
- api/apps/dataset_api.py +4 -1
- docs/references/ragflow_api.md +347 -1
- sdk/python/test/test_document.py +1 -2
api/apps/dataset_api.py
CHANGED
|
@@ -768,7 +768,10 @@ def show_parsing_status(dataset_id, document_id):
|
|
| 768 |
_, doc = DocumentService.get_by_id(document_id) # get doc object
|
| 769 |
doc_attributes = doc.to_dict()
|
| 770 |
|
| 771 |
-
return construct_json_result(
|
|
|
|
|
|
|
|
|
|
| 772 |
except Exception as e:
|
| 773 |
return construct_error_response(e)
|
| 774 |
# ----------------------------list the chunks of the file-----------------------------------------------------
|
|
|
|
| 768 |
_, doc = DocumentService.get_by_id(document_id) # get doc object
|
| 769 |
doc_attributes = doc.to_dict()
|
| 770 |
|
| 771 |
+
return construct_json_result(
|
| 772 |
+
data={"progress": doc_attributes["progress"], "status": TaskStatus(doc_attributes["status"]).name},
|
| 773 |
+
code=RetCode.SUCCESS
|
| 774 |
+
)
|
| 775 |
except Exception as e:
|
| 776 |
return construct_error_response(e)
|
| 777 |
# ----------------------------list the chunks of the file-----------------------------------------------------
|
docs/references/ragflow_api.md
CHANGED
|
@@ -428,7 +428,7 @@ This method deletes documents for a specific user.
|
|
| 428 |
|
| 429 |
## List documents
|
| 430 |
|
| 431 |
-
This method
|
| 432 |
|
| 433 |
### Request
|
| 434 |
|
|
@@ -532,4 +532,350 @@ This method deletes documents for a specific user.
|
|
| 532 |
"message": "IndexError('Offset is out of the valid range.')"
|
| 533 |
}
|
| 534 |
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 535 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 428 |
|
| 429 |
## List documents
|
| 430 |
|
| 431 |
+
This method lists documents for a specific user.
|
| 432 |
|
| 433 |
### Request
|
| 434 |
|
|
|
|
| 532 |
"message": "IndexError('Offset is out of the valid range.')"
|
| 533 |
}
|
| 534 |
```
|
| 535 |
+
## Update the details of the document
|
| 536 |
+
|
| 537 |
+
This method updates the details, including the name, enable and template type of a specific document for a specific user.
|
| 538 |
+
|
| 539 |
+
### Request
|
| 540 |
+
|
| 541 |
+
#### Request URI
|
| 542 |
+
|
| 543 |
+
| Method | Request URI |
|
| 544 |
+
|--------|-------------------------------------------------|
|
| 545 |
+
| PUT | `/dataset/{dataset_id}/documents/{document_id}` |
|
| 546 |
+
|
| 547 |
+
|
| 548 |
+
#### Request parameter
|
| 549 |
+
|
| 550 |
+
| Name | Type | Required | Description |
|
| 551 |
+
|--------------|--------|----------|------------------------------------------------------------------------------------------------------------|
|
| 552 |
+
| `dataset_id` | string | Yes | The ID of the dataset. Call ['GET' /dataset](#create-dataset) to retrieve the ID. |
|
| 553 |
+
| `document_id` | string | Yes | The ID of the document. Call ['GET' /document](#list-documents) to retrieve the ID. |
|
| 554 |
+
|
| 555 |
+
### Response
|
| 556 |
+
|
| 557 |
+
### Successful Response
|
| 558 |
+
|
| 559 |
+
```json
|
| 560 |
+
{
|
| 561 |
+
"code": 0,
|
| 562 |
+
"data": {
|
| 563 |
+
"chunk_num": 0,
|
| 564 |
+
"create_date": "Mon, 15 Jul 2024 16:55:03 GMT",
|
| 565 |
+
"create_time": 1721033703914,
|
| 566 |
+
"created_by": "b48110a0286411ef994a3043d7ee537e",
|
| 567 |
+
"id": "ed30167a428711efab193043d7ee537e",
|
| 568 |
+
"kb_id": "ed2d8770428711efaf583043d7ee537e",
|
| 569 |
+
"location": "test.txt",
|
| 570 |
+
"name": "new_name.txt",
|
| 571 |
+
"parser_config": {
|
| 572 |
+
"pages": [
|
| 573 |
+
[1, 1000000]
|
| 574 |
+
]
|
| 575 |
+
},
|
| 576 |
+
"parser_id": "naive",
|
| 577 |
+
"process_begin_at": null,
|
| 578 |
+
"process_duration": 0.0,
|
| 579 |
+
"progress": 0.0,
|
| 580 |
+
"progress_msg": "",
|
| 581 |
+
"run": "0",
|
| 582 |
+
"size": 14,
|
| 583 |
+
"source_type": "local",
|
| 584 |
+
"status": "1",
|
| 585 |
+
"thumbnail": null,
|
| 586 |
+
"token_num": 0,
|
| 587 |
+
"type": "doc",
|
| 588 |
+
"update_date": "Mon, 15 Jul 2024 16:55:03 GMT",
|
| 589 |
+
"update_time": 1721033703934
|
| 590 |
+
},
|
| 591 |
+
"message": "Success"
|
| 592 |
+
}
|
| 593 |
+
```
|
| 594 |
+
|
| 595 |
+
### Response for updating a document which does not exist.
|
| 596 |
+
|
| 597 |
+
```json
|
| 598 |
+
{
|
| 599 |
+
"code": 101,
|
| 600 |
+
"message": "This document weird_doc_id cannot be found!"
|
| 601 |
+
}
|
| 602 |
+
```
|
| 603 |
+
|
| 604 |
+
### Response for updating a document without giving parameters.
|
| 605 |
+
```json
|
| 606 |
+
{
|
| 607 |
+
"code": 102,
|
| 608 |
+
"message": "Please input at least one parameter that you want to update!"
|
| 609 |
+
}
|
| 610 |
+
```
|
| 611 |
+
|
| 612 |
+
### Response for updating a document in the nonexistent dataset.
|
| 613 |
+
```json
|
| 614 |
+
{
|
| 615 |
+
"code": 102,
|
| 616 |
+
"message": "This dataset fake_dataset_id cannot be found!"
|
| 617 |
+
}
|
| 618 |
+
```
|
| 619 |
+
|
| 620 |
+
### Response for updating a document with an extension name that differs from its original.
|
| 621 |
+
```json
|
| 622 |
+
{
|
| 623 |
+
"code": 101,
|
| 624 |
+
"data": false,
|
| 625 |
+
"message": "The extension of file cannot be changed"
|
| 626 |
+
}
|
| 627 |
+
```
|
| 628 |
+
|
| 629 |
+
### Response for updating a document with a duplicate name.
|
| 630 |
+
```json
|
| 631 |
+
{
|
| 632 |
+
"code": 101,
|
| 633 |
+
"message": "Duplicated document name in the same dataset."
|
| 634 |
+
}
|
| 635 |
+
```
|
| 636 |
+
|
| 637 |
+
### Response for updating a document's illegal parameter.
|
| 638 |
+
```json
|
| 639 |
+
{
|
| 640 |
+
"code": 101,
|
| 641 |
+
"message": "illegal_parameter is an illegal parameter."
|
| 642 |
+
}
|
| 643 |
+
```
|
| 644 |
+
|
| 645 |
+
### Response for updating a document's name without its name value.
|
| 646 |
+
```json
|
| 647 |
+
{
|
| 648 |
+
"code": 102,
|
| 649 |
+
"message": "There is no new name."
|
| 650 |
+
}
|
| 651 |
+
```
|
| 652 |
+
|
| 653 |
+
### Response for updating a document's with giving illegal enable's value.
|
| 654 |
+
```json
|
| 655 |
+
{
|
| 656 |
+
"code": 102,
|
| 657 |
+
"message": "Illegal value '?' for 'enable' field."
|
| 658 |
+
}
|
| 659 |
+
```
|
| 660 |
+
|
| 661 |
+
## Download the document
|
| 662 |
+
|
| 663 |
+
This method downloads a specific document for a specific user.
|
| 664 |
+
|
| 665 |
+
### Request
|
| 666 |
+
|
| 667 |
+
#### Request URI
|
| 668 |
+
|
| 669 |
+
| Method | Request URI |
|
| 670 |
+
|--------|-------------------------------------------------|
|
| 671 |
+
| GET | `/dataset/{dataset_id}/documents/{document_id}` |
|
| 672 |
+
|
| 673 |
+
|
| 674 |
+
#### Request parameter
|
| 675 |
+
|
| 676 |
+
| Name | Type | Required | Description |
|
| 677 |
+
|--------------|--------|----------|------------------------------------------------------------------------------------------------------------|
|
| 678 |
+
| `dataset_id` | string | Yes | The ID of the dataset. Call ['GET' /dataset](#create-dataset) to retrieve the ID. |
|
| 679 |
+
| `document_id` | string | Yes | The ID of the document. Call ['GET' /document](#list-documents) to retrieve the ID. |
|
| 680 |
+
|
| 681 |
+
### Response
|
| 682 |
+
|
| 683 |
+
### Successful Response
|
| 684 |
+
|
| 685 |
+
```json
|
| 686 |
+
{
|
| 687 |
+
"code": "0",
|
| 688 |
+
"data": "b'test\\ntest\\ntest'"
|
| 689 |
+
}
|
| 690 |
+
```
|
| 691 |
+
|
| 692 |
+
### Response for downloading a document which does not exist.
|
| 693 |
+
|
| 694 |
+
```json
|
| 695 |
+
{
|
| 696 |
+
"code": 101,
|
| 697 |
+
"message": "This document 'imagination.txt' cannot be found!"
|
| 698 |
+
}
|
| 699 |
+
```
|
| 700 |
+
|
| 701 |
+
### Response for downloading a document in the nonexistent dataset.
|
| 702 |
+
```json
|
| 703 |
+
{
|
| 704 |
+
"code": 102,
|
| 705 |
+
"message": "This dataset 'imagination' cannot be found!"
|
| 706 |
+
}
|
| 707 |
+
```
|
| 708 |
+
|
| 709 |
+
### Response for downloading an empty document.
|
| 710 |
+
```json
|
| 711 |
+
{
|
| 712 |
+
"code": 102,
|
| 713 |
+
"message": "This file is empty."
|
| 714 |
+
}
|
| 715 |
+
```
|
| 716 |
+
|
| 717 |
+
## Start parsing a document
|
| 718 |
|
| 719 |
+
This method enables a specific document to start parsing for a specific user.
|
| 720 |
+
|
| 721 |
+
### Request
|
| 722 |
+
|
| 723 |
+
#### Request URI
|
| 724 |
+
|
| 725 |
+
| Method | Request URI |
|
| 726 |
+
|--------|--------------------------------------------------------|
|
| 727 |
+
| POST | `/dataset/{dataset_id}/documents/{document_id}/status` |
|
| 728 |
+
|
| 729 |
+
|
| 730 |
+
#### Request parameter
|
| 731 |
+
|
| 732 |
+
| Name | Type | Required | Description |
|
| 733 |
+
|--------------|--------|----------|------------------------------------------------------------------------------------------------------------|
|
| 734 |
+
| `dataset_id` | string | Yes | The ID of the dataset. Call ['GET' /dataset](#create-dataset) to retrieve the ID. |
|
| 735 |
+
| `document_id` | string | Yes | The ID of the document. Call ['GET' /document](#list-documents) to retrieve the ID. |
|
| 736 |
+
|
| 737 |
+
### Response
|
| 738 |
+
|
| 739 |
+
### Successful Response
|
| 740 |
+
|
| 741 |
+
```json
|
| 742 |
+
{
|
| 743 |
+
"code": 0,
|
| 744 |
+
"message": ""
|
| 745 |
+
}
|
| 746 |
+
```
|
| 747 |
+
|
| 748 |
+
### Response for parsing a document which does not exist.
|
| 749 |
+
|
| 750 |
+
```json
|
| 751 |
+
{
|
| 752 |
+
"code": 101,
|
| 753 |
+
"message": "This document 'imagination.txt' cannot be found!"
|
| 754 |
+
}
|
| 755 |
+
```
|
| 756 |
+
|
| 757 |
+
### Response for parsing a document in the nonexistent dataset.
|
| 758 |
+
```json
|
| 759 |
+
{
|
| 760 |
+
"code": 102,
|
| 761 |
+
"message": "This dataset 'imagination.txt' cannot be found!"
|
| 762 |
+
}
|
| 763 |
+
```
|
| 764 |
+
|
| 765 |
+
### Response for parsing an empty document.
|
| 766 |
+
```json
|
| 767 |
+
{
|
| 768 |
+
"code": 0,
|
| 769 |
+
"message": "Empty data in the document: empty.txt;"
|
| 770 |
+
}
|
| 771 |
+
```
|
| 772 |
+
|
| 773 |
+
## Start parsing multiple documents
|
| 774 |
+
|
| 775 |
+
This method enables multiple documents, including all documents in the specific dataset or specified documents, to start parsing for a specific user.
|
| 776 |
+
|
| 777 |
+
### Request
|
| 778 |
+
|
| 779 |
+
#### Request URI
|
| 780 |
+
|
| 781 |
+
| Method | Request URI |
|
| 782 |
+
|--------|-------------------------------------------------------|
|
| 783 |
+
| POST | `/dataset/{dataset_id}/documents/status` |
|
| 784 |
+
|
| 785 |
+
|
| 786 |
+
#### Request parameter
|
| 787 |
+
|
| 788 |
+
| Name | Type | Required | Description |
|
| 789 |
+
|--------------|--------|----------|-----------------------------------------------------------------------------------------------------------------------------------|
|
| 790 |
+
| `dataset_id` | string | Yes | The ID of the dataset. Call ['GET' /dataset](#create-dataset) to retrieve the ID. |
|
| 791 |
+
| `document_id` | string | Yes | The ID of the document. Call ['GET' /document](#list-documents) to retrieve the ID. |
|
| 792 |
+
| `doc_ids` | list | No | The document IDs of the documents that the user would like to parse. Default: None, means all documents in the specified dataset. |
|
| 793 |
+
### Response
|
| 794 |
+
|
| 795 |
+
### Successful Response
|
| 796 |
+
|
| 797 |
+
```json
|
| 798 |
+
{
|
| 799 |
+
"code": 0,
|
| 800 |
+
"data": true,
|
| 801 |
+
"message": ""
|
| 802 |
+
}
|
| 803 |
+
```
|
| 804 |
+
|
| 805 |
+
### Response for parsing documents which does not exist.
|
| 806 |
+
|
| 807 |
+
```json
|
| 808 |
+
{
|
| 809 |
+
"code": 101,
|
| 810 |
+
"message": "This document 'imagination.txt' cannot be found!"
|
| 811 |
+
}
|
| 812 |
+
```
|
| 813 |
+
|
| 814 |
+
### Response for parsing documents in the nonexistent dataset.
|
| 815 |
+
```json
|
| 816 |
+
{
|
| 817 |
+
"code": 102,
|
| 818 |
+
"message": "This dataset 'imagination' cannot be found!"
|
| 819 |
+
}
|
| 820 |
+
```
|
| 821 |
+
|
| 822 |
+
### Response for parsing documents, one of which is empty.
|
| 823 |
+
```json
|
| 824 |
+
{
|
| 825 |
+
"code": 0,
|
| 826 |
+
"data": true,
|
| 827 |
+
"message": "Empty data in the document: empty.txt; "
|
| 828 |
+
}
|
| 829 |
+
```
|
| 830 |
+
|
| 831 |
+
## Show the parsing status of the document
|
| 832 |
+
|
| 833 |
+
This method shows the parsing status of the document for a specific user.
|
| 834 |
+
|
| 835 |
+
### Request
|
| 836 |
+
|
| 837 |
+
#### Request URI
|
| 838 |
+
|
| 839 |
+
| Method | Request URI |
|
| 840 |
+
|--------|-------------------------------------------------------|
|
| 841 |
+
| GET | `/dataset/{dataset_id}/documents/status` |
|
| 842 |
+
|
| 843 |
+
|
| 844 |
+
#### Request parameter
|
| 845 |
+
|
| 846 |
+
| Name | Type | Required | Description |
|
| 847 |
+
|--------------|--------|----------|-----------------------------------------------------------------------------------------------------------------------------------|
|
| 848 |
+
| `dataset_id` | string | Yes | The ID of the dataset. Call ['GET' /dataset](#create-dataset) to retrieve the ID. |
|
| 849 |
+
| `document_id` | string | Yes | The ID of the document. Call ['GET' /document](#list-documents) to retrieve the ID. |
|
| 850 |
+
|
| 851 |
+
### Response
|
| 852 |
+
|
| 853 |
+
### Successful Response
|
| 854 |
+
|
| 855 |
+
```json
|
| 856 |
+
{
|
| 857 |
+
"code": 0,
|
| 858 |
+
"data": {
|
| 859 |
+
"progress": 0.0,
|
| 860 |
+
"status": "RUNNING"
|
| 861 |
+
},
|
| 862 |
+
"message": "success"
|
| 863 |
+
}
|
| 864 |
+
```
|
| 865 |
+
|
| 866 |
+
### Response for showing the parsing status of a document which does not exist.
|
| 867 |
+
|
| 868 |
+
```json
|
| 869 |
+
{
|
| 870 |
+
"code": 102,
|
| 871 |
+
"message": "This document: 'imagination.txt' is not a valid document."
|
| 872 |
+
}
|
| 873 |
+
```
|
| 874 |
+
|
| 875 |
+
### Response for showing the parsing status of a document in the nonexistent dataset.
|
| 876 |
+
```json
|
| 877 |
+
{
|
| 878 |
+
"code": 102,
|
| 879 |
+
"message": "This dataset 'imagination' cannot be found!"
|
| 880 |
+
}
|
| 881 |
+
```
|
sdk/python/test/test_document.py
CHANGED
|
@@ -555,7 +555,6 @@ class TestFile(TestSdk):
|
|
| 555 |
"illegal_parameter": "0"
|
| 556 |
}
|
| 557 |
update_res = ragflow.update_file(created_res_id, doc_id, **params)
|
| 558 |
-
|
| 559 |
assert (update_res["code"] == RetCode.ARGUMENT_ERROR and
|
| 560 |
update_res["message"] == "illegal_parameter is an illegal parameter.")
|
| 561 |
|
|
@@ -969,7 +968,7 @@ class TestFile(TestSdk):
|
|
| 969 |
assert res["code"] == RetCode.SUCCESS and res["message"] == ""
|
| 970 |
# show status
|
| 971 |
status_res = ragflow.show_parsing_status(created_res_id, doc_id)
|
| 972 |
-
assert status_res["code"] == RetCode.SUCCESS and status_res["data"]["status"] == "
|
| 973 |
|
| 974 |
def test_show_status_nonexistent_document(self):
|
| 975 |
"""
|
|
|
|
| 555 |
"illegal_parameter": "0"
|
| 556 |
}
|
| 557 |
update_res = ragflow.update_file(created_res_id, doc_id, **params)
|
|
|
|
| 558 |
assert (update_res["code"] == RetCode.ARGUMENT_ERROR and
|
| 559 |
update_res["message"] == "illegal_parameter is an illegal parameter.")
|
| 560 |
|
|
|
|
| 968 |
assert res["code"] == RetCode.SUCCESS and res["message"] == ""
|
| 969 |
# show status
|
| 970 |
status_res = ragflow.show_parsing_status(created_res_id, doc_id)
|
| 971 |
+
assert status_res["code"] == RetCode.SUCCESS and status_res["data"]["status"] == "RUNNING"
|
| 972 |
|
| 973 |
def test_show_status_nonexistent_document(self):
|
| 974 |
"""
|