notion-dotnet / notion-sdk-net Goto Github PK
View Code? Open in Web Editor NEWA Notion SDK for .Net
License: MIT License
A Notion SDK for .Net
License: MIT License
Currently, Sonar Cloud analysis workflow doesn't work for forked branches. Find a way to run Sonar Cloud analysis for PR created from forked branches.
There is recent update from Notion API. The number property type in databases now supports additional currency options.
The new options are:
This impacts the number configuration of databases.
Notion API change log: https://developers.notion.com/changelog/number-properties-now-support-more-currency-formats
Add examples solution to use the library in the below types of projects
Looks like IObject.Object
(ObjectType
enum) writes as number
NotionApiException
body failed validation: body.children[0].object should be
"block", instead was
2.
at Notion.Client.RestClient.SendAsync(String requestUri, HttpMethod httpMethod, IDictionary`2 queryParams, IDictionary`2 headers, Action`1 attachContent, CancellationToken cancellationToken) in F:\AllProjects\CompanyName\ToolsAndAutomatization\TasksStructureAutomatization\NotionDotNet.NotionSdkNet\Src\Notion.Client\RestClient\RestClient.cs:line 102
at Notion.Client.RestClient.PatchAsync[T](String uri, Object body, IDictionary`2 queryParams, IDictionary`2 headers, JsonSerializerSettings serializerSettings, CancellationToken cancellationToken) in F:\AllProjects\CompanyName\ToolsAndAutomatization\TasksStructureAutomatization\NotionDotNet.NotionSdkNet\Src\Notion.Client\RestClient\RestClient.cs:line 142
at Notion.Client.BlocksClient.AppendChildrenAsync(String blockId, BlocksAppendChildrenParameters parameters) in F:\AllProjects\CompanyName\ToolsAndAutomatization\TasksStructureAutomatization\NotionDotNet.NotionSdkNet\Src\Notion.Client\Api\Blocks\BlocksClient.cs:line 48
at CompanyName.ProjectStructureAutomatization.App.AppendPageRelationsRecursive(String parentId, Page page) in F:\AllProjects\CompanyName\ToolsAndAutomatization\TasksStructureAutomatization\CompanyName.TasksStructureAutomatization\App.cs:line 120
at Notion.Client.RestClient.SendAsync(String requestUri, HttpMethod httpMethod, IDictionary`2 queryParams, IDictionary`2 headers, Action`1 attachContent, CancellationToken cancellationToken) in F:\AllProjects\CompanyName\ToolsAndAutomatization\TasksStructureAutomatization\NotionDotNet.NotionSdkNet\Src\Notion.Client\RestClient\RestClient.cs:line 102
at Notion.Client.RestClient.PatchAsync[T](String uri, Object body, IDictionary`2 queryParams, IDictionary`2 headers, JsonSerializerSettings serializerSettings, CancellationToken cancellationToken) in F:\AllProjects\CompanyName\ToolsAndAutomatization\TasksStructureAutomatization\NotionDotNet.NotionSdkNet\Src\Notion.Client\RestClient\RestClient.cs:line 142
at Notion.Client.BlocksClient.AppendChildrenAsync(String blockId, BlocksAppendChildrenParameters parameters) in F:\AllProjects\CompanyName\ToolsAndAutomatization\TasksStructureAutomatization\NotionDotNet.NotionSdkNet\Src\Notion.Client\Api\Blocks\BlocksClient.cs:line 48
at CompanyName.ProjectStructureAutomatization.App.AppendPageRelationsRecursive(String parentId, Page page) in F:\AllProjects\CompanyName\ToolsAndAutomatization\TasksStructureAutomatization\CompanyName.TasksStructureAutomatization\App.cs:line 120
Update page api provides ability to achieve or delete a page by setting the achieve property when updating the page.
Set the archived parameter to true to archive or delete a page. For a page that is already archived, you can un-archive or restore that page by making the same request and setting archived to false.
API reference: https://developers.notion.com/reference/patch-page#archive-delete-a-page
A block object represents content within Notion. Blocks can be text, lists, media, and more. A page is a type of block, too!
API's
We already have defined the .editorcofig
file to maintain consistent coding style. We can format the code using the dotnet format
tool locally but it is very easy to miss.
To make sure every code committed in the repo follow the consistent code style we should add a step in CI build pipeline to validate the code style.
There is no property "file" https://developers.notion.com/reference/database#database-property. renamed to "files"
Endpoints
To be consistent with the Notion application, only users of type "people" can be mentioned in rich text objects or in people properties of databases. Trying to include users of type "bot" will return a validation error. Existing mentions of bot users is unaffected.
Notion API changelog reference: https://developers.notion.com/changelog/user-mentions-can-only-be-of-people
We may not need to add validation in the fetch database object but when we are going to add support for the create database then we may need to consider adding the validation on the models we use to create database. #56
Add branch protection rule which will require CI / build
status to pass before branches can be merged into Main.
This will help users to see the supported endpoints by this library easily compare to diving in the code.
You can now use the Notion API to create a database as a subpage of an existing page.
Currently supported property types are title
, rich_text
, number
, select
. multi_select
, date
, people
, files
, checkbox
, url
, email
, phone_number
, created_time
, created_by
, last_edited_time
, last_edited_by
.
Notion API changelog: https://developers.notion.com/changelog/create-new-databases-with-post-databases
curl --location --request POST 'https://api.notion.com/v1/databases/' \
--header 'Authorization: Bearer '"$NOTION_API_KEY"'' \
--header 'Content-Type: application/json' \
--header 'Notion-Version: 2021-05-13' \
--data '{
"parent": {
"type": "page_id",
"page_id": "98ad959b-2b6a-4774-80ee-00246fb0ea9b"
},
"title": [
{
"type": "text",
"text": {
"content": "Grocery List",
"link": null
}
}
],
"properties": {
"Name": {
"title": {}
},
"Description": {
"rich_text": {}
},
"In stock": {
"checkbox": {}
},
"Food group": {
"select": {
"options": [
{
"name": "🥦Vegetable",
"color": "green"
},
{
"name": "🍎Fruit",
"color": "red"
},
{
"name": "💪Protein",
"color": "yellow"
}
]
}
},
"Price": {
"number": {
"format": "dollar"
}
},
"Last ordered": {
"date": {}
},
"Store availability": {
"type": "multi_select",
"multi_select": {
"options": [
{
"name": "Duc Loi Market",
"color": "blue"
},
{
"name": "Rainbow Grocery",
"color": "gray"
},
{
"name": "Nijiya Market",
"color": "purple"
},
{
"name": "Gus'\''s Community Market",
"color": "yellow"
}
]
}
},
"+1": {
"people": {}
},
"Photo": {
"files": {}
}
}
}'
{
"object": "database",
"id": "bc1211ca-e3f1-4939-ae34-5260b16f627c",
"created_time": "2021-07-08T23:50:00.000Z",
"last_edited_time": "2021-07-08T23:50:00.000Z",
"title": [
{
"type": "text",
"text": {
"content": "Grocery List",
"link": null
},
"annotations": {
"bold": false,
"italic": false,
"strikethrough": false,
"underline": false,
"code": false,
"color": "default"
},
"plain_text": "Grocery List",
"href": null
}
],
"properties": {
"+1": {
"id": "PNEQ",
"type": "people",
"people": {}
},
"In stock": {
"id": "V>GQ",
"type": "checkbox",
"checkbox": {}
},
"Price": {
"id": "V@]u",
"type": "number",
"number": {
"format": "dollar"
}
},
"Description": {
"id": "V}lX",
"type": "rich_text",
"rich_text": {}
},
"Last ordered": {
"id": "eVnV",
"type": "date",
"date": {}
},
"Store availability": {
"id": "s}Kq",
"type": "multi_select",
"multi_select": {
"options": [
{
"id": "cb79b393-d1c1-4528-b517-c450859de766",
"name": "Duc Loi Market",
"color": "blue"
},
{
"id": "58aae162-75d4-403b-a793-3bc7308e4cd2",
"name": "Rainbow Grocery",
"color": "gray"
},
{
"id": "22d0f199-babc-44ff-bd80-a9eae3e3fcbf",
"name": "Nijiya Market",
"color": "purple"
},
{
"id": "0d069987-ffb0-4347-bde2-8e4068003dbc",
"name": "Gus's Community Market",
"color": "yellow"
}
]
}
},
"Photo": {
"id": "yfiK",
"type": "files",
"files": {}
},
"Food group": {
"id": "|JKd",
"type": "select",
"select": {
"options": [
{
"id": "6d4523fa-88cb-4ffd-9364-1e39d0f4e566",
"name": "🥦Vegetable",
"color": "green"
},
{
"id": "268d7e75-de8f-4c4b-8b9d-de0f97021833",
"name": "🍎Fruit",
"color": "red"
},
{
"id": "1b234a00-dc97-489c-b987-829264cfdfef",
"name": "💪Protein",
"color": "yellow"
}
]
}
},
"Name": {
"id": "title",
"type": "title",
"title": {}
}
},
"parent": {
"type": "page_id",
"page_id": "98ad959b-2b6a-4774-80ee-00246fb0ea9b"
}
}
You can now use the Notion API to update databases!
Supported updates are:
Note that updating the name and color select and multi select options is not supported.
curl --location --request PATCH 'https://api.notion.com/v1/databases/668d797c-76fa-4934-9b05-ad288df2d136' \
--header 'Authorization: Bearer '"$NOTION_API_KEY"'' \
--header 'Content-Type: application/json' \
--header 'Notion-Version: 2021-07-27' \
--data '{
"title": [
{
"text": {
"content": "Today'\''s grocery list"
}
}
],
"properties": {
"+1": null,
"Photo": {
"url": {}
},
"Store availability": {
"multi_select": {
"options": [
{
"name": "Duc Loi Market"
},
{
"name": "Rainbow Grocery"
},
{
"name": "Gus'\''s Community Market"
},
{
"name": "The Good Life Grocery",
"color": "orange"
}
]
}
}
}
}'
{
"object": "database",
"id": "668d797c-76fa-4934-9b05-ad288df2d136",
"created_time": "2020-03-17T19:10:00.000Z",
"last_edited_time": "2021-08-11T17:26:00.000Z",
"parent": {
"type": "page_id",
"page_id": "48f8fee9-cd79-4180-bc2f-ec0398253067"
},
"title": [
{
"type": "text",
"text": {
"content": "Today'\''s grocery list",
"link": null
},
"annotations": {
"bold": false,
"italic": false,
"strikethrough": false,
"underline": false,
"code": false,
"color": "default"
},
"plain_text": "Today'\''s grocery list",
"href": null
}
],
"properties": {
"Name": {
"id": "title",
"type": "title",
"title": {}
},
"Description": {
"id": "J@cS",
"type": "rich_text",
"rich_text": {}
},
"In stock": {
"id": "{xY`",
"type": "checkbox",
"checkbox": {}
},
"Food group": {
"id": "TJmr",
"type": "select",
"select": {
"options": [
{
"id": "96eb622f-4b88-4283-919d-ece2fbed3841",
"name": "🥦Vegetable",
"color": "green"
},
{
"id": "bb443819-81dc-46fb-882d-ebee6e22c432",
"name": "🍎Fruit",
"color": "red"
},
{
"id": "7da9d1b9-8685-472e-9da3-3af57bdb221e",
"name": "💪Protein",
"color": "yellow"
}
]
}
},
"Price": {
"id": "cU^N",
"type": "number",
"number": {
"format": "dollar"
}
},
"Cost of next trip": {
"id": "p:sC",
"type": "formula",
"formula": {
"value": "if(prop(\"In stock\"), 0, prop(\"Price\"))"
}
},
"Last ordered": {
"id": "]\\R[",
"type": "date",
"date": {}
},
"Meals": {
"type": "relation",
"relation": {
"database": "668d797c-76fa-4934-9b05-ad288df2d136",
"synced_property_name": null
}
},
"Number of meals": {
"id": "Z\\Eh",
"type": "rollup",
"rollup": {
"rollup_property_name": "Name",
"relation_property_name": "Meals",
"rollup_property_id": "title",
"relation_property_id": "mxp^",
"function": "count"
}
},
"Store availability": {
"type": "multi_select",
"multi_select": {
"options": [
[
{
"id": "d209b920-212c-4040-9d4a-bdf349dd8b2a",
"name": "Duc Loi Market",
"color": "blue"
},
{
"id": "70104074-0f91-467b-9787-00d59e6e1e41",
"name": "Rainbow Grocery",
"color": "gray"
},
{
"id": "6c3867c5-d542-4f84-b6e9-a420c43094e7",
"name": "Gus's Community Market",
"color": "yellow"
},
{
"id": "a62fbb5f-fed4-44a4-8cac-cba5f518c1a1",
"name": "Good life grocery",
"color": "orange"
}
]
]
}
}
"Photo": {
"id": "aTIT",
"type": "url",
"url": {}
}
}
}
Notion API changelog ref: https://developers.notion.com/changelog/update-existing-databases-with-patch-v1databases
CreateAsync(newPage) in PageClientTests.cs should be getting an exception for you since it is missing Parent.
I based add page in my application on your test code and got exception. Works fine after adding Parent.
Your toolkit is exceptional. Thank you for creating it.
Boolean property in Src/Notion.Client/Models/PropertyValue/FormulaPropertyValue.cs
class can be Nullable.
Database objects describe the property schema of a database in Notion.
Add support for below API's
Responses from the API use HTTP response codes are used to indicate general classes of success and error. Error responses contain more detail about the error in the response body, in the "code" and "message" properties.
Database objects now return a parent property. Databases can have pages or workspaces as parents.
Parent property example JSON
{
"parent": {
"type": "page_id",
"page_id": "b8595b75-abd1-4cad-8dfe-f935a8ef57cb"
}
Notion API changelog ref: https://developers.notion.com/changelog/database-objects-now-return-parent
Add Continuous security analysis. https://lgtm.com/
A code analysis platform for finding zero-days and preventing critical vulnerabilities. Prevent bugs from ever making it to your project by using automated reviews that let you know when your code changes would introduce alerts into your project.
Database property objects now include the field name with the property name as it appears in Notion.
Notion API changelog: https://developers.notion.com/changelog/database-property-objects-now-include-property-name
EditorConfig helps maintain consistent coding styles for multiple developers working on the same project across various editors and IDEs.
You can now retrieve and update block objects with the Notion API! The PATCH endpoint currently supports updating paragraph, heading_1, heading_2, heading_3, bulleted_list_item, numbered_list_item, toggle and to_do blocks.
The Retrieve a Block endpoint returns a Block Object.
Example GET Request
curl 'https://api.notion.com/v1/blocks/9bc30ad4-9373-46a5-84ab-0a7845ee52e6' \
-H 'Authorization: Bearer '"$NOTION_API_KEY"'' \
-H 'Notion-Version: 2021-05-13'
Example GET Response
{
"object": "block",
"id": "9bc30ad4-9373-46a5-84ab-0a7845ee52e6",
"created_time": "2021-03-16T16:31:00.000Z",
"last_edited_time": "2021-03-16T16:32:00.000Z",
"has_children": false,
"type": "to_do",
"to_do": {
"text": [
{
"type": "text",
"text": {
"content": "Lacinato kale",
"link": null
},
"annotations": {
"bold": false,
"italic": false,
"strikethrough": false,
"underline": false,
"code": false,
"color": "default"
},
"plain_text": "Lacinato kale",
"href": null
}
],
"checked": false
}
}
The new PATCH /v1/blocks/:id endpoint supports updating block content (the properties within the block type object) and returns the updated Block Object, same as the GET
endpoint shown above. See the Update a Block documentation for more detail.
Example PATCH Request
curl https://api.notion.com/v1/blocks/9bc30ad4-9373-46a5-84ab-0a7845ee52e6 \
-H 'Authorization: Bearer '"$NOTION_API_KEY"'' \
-H "Content-Type: application/json" \
-H "Notion-Version: 2021-05-13" \
-X PATCH \
--data '{
"to_do": {
"text": [{
"text": { "content": "Lacinato kale" }
}],
"checked": false
}
}'
Notion API reference ref: https://developers.notion.com/changelog/retrieve-and-update-blocks-with-get-and-patch-v1blocksid
@Kurupapuru found this issue and given fix in the PR #31 - I'm logging the issue for that and will create a separate PR to fix this. So I can go ahead and create a release with the fix.
Recently NuGet provides ability to pack Readme.md file with your package. This is still in preview but when you actually visit the NuGet.org they already starting showing the readme file. This will help users to get started easily instead of them visiting the GitHub to read the readme files.
Code in RestClient.BuildException
method try to access the errorResponse.Message
where errorResponse
could be null and may throw exception.
private static async Task<Exception> BuildException(HttpResponseMessage response)
{
var errorBody = await response.Content.ReadAsStringAsync();
NotionApiErrorResponse errorResponse = null;
if (!string.IsNullOrWhiteSpace(errorBody))
{
try
{
errorResponse = JsonConvert.DeserializeObject<NotionApiErrorResponse>(errorBody);
}
catch
{
}
}
return new NotionApiException(response.StatusCode, errorResponse?.ErrorCode, errorResponse.Message);
}
It should be changed to errorResponse?.Message
.
Page objects now return the web address of the page in the url
key.
Note: This impacts endpoints that return page object:
Example JSON Response
{
"object": "page",
"id": "251d2b5f-268c-4de2-afe9-c71ff92ca95c",
"created_time": "2020-03-17T19:10:04.968Z",
"last_edited_time": "2020-03-17T21:49:37.913Z",
"parent": {
"type": "database_id",
"database_id": "48f8fee9-cd79-4180-bc2f-ec0398253067"
},
"archived": false,
"url": "https://www.notion.so/Avocado-251d2b5f268c4de2afe9c71ff92ca95c",
"properties": {
"Name": {
"id": "title",
"type": "title",
"title": [
{
"type": "text",
"text": {
"content": "Avocado",
"link": null
},
"annotations": {
"bold": false,
"italic": false,
"strikethrough": false,
"underline": false,
"code": false,
"color": "default"
},
"plain_text": "Avocado",
"href": null
}
]
}
}
}
The Page object contains the property values of a single Notion page.
API's
So currently creating just one filter takes 8 lines of code. It is not that much, but making a compound filter with both "and" and "or" condtions takes up to 40 lines, which I would like to optimize.
Currently initialization of a filter class looks like this:
var filter = new PeopleFilter
{
Property = "Assignee",
People = new PeopleFilter.Condition
{
IsNotEmpty = true
}
};
And I would like it to look something like this:
var filter = new PeopleFilter("Assignee", PeopleFilter.ConditionType.IsNotEmpty, true);
For this I can add ConditionType
enums to filter classes and choose which property to assign in [Filter].Condition initialization automatically. So I expect it to look something like this:
public class PeopleFilter : SinglePropertyFilter
{
public Condition People { get; set; }
public PeopleFilter(string property, PeopleFilter.ConditionType conditionType, object value)
{
var condition = new Condition();
switch (conditionType)
{
case ConditionType.Contains:
condition.Contains = (string)value;
break;
case ConditionType.DoesNotContain:
condition.DoesNotContain = (string)value;
break;
case ConditionType.IsEmpty:
condition.IsEmpty = (bool)value;
break;
case ConditionType.IsNotEmpty:
condition.IsEmpty = (bool)value;
break;
default:
break;
}
Property = property;
People = condition;
}
public class Condition
{
// ...
}
public enum ConditionType
{
Contains,
DoesNotContain,
IsEmpty,
IsNotEmpty,
}
}
Of course there are possible issues with typing value
wrongfully. I also thought about making constructors overloads for each possible value type, but that would require creating separate enums for each type.
Please, let me know your thoughts on this idea.
Source Link enables a great source debugging experience for your users, by adding source control metadata to your built assets
SonarCloud is a cloud-based code analysis service designed to detect code quality issues, continuously ensuring the maintainability, reliability and security of your code.
I tripped over this bug trying to make a simple bot fetching tasks from my Notion.
Here is the code I executed. I suspected I used filters wrong, but I couldn't find any other other way to do it.
var client = new NotionClient(new ClientOptions
{
AuthToken = "MY_TOKEN"
});
var dateFilter = new DateFilter
{
Property = "When",
OnOrAfter = DateTime.Now,
};
var queryParams = new DatabasesQueryParameters
{
Filter = dateFilter,
};
var databaseId = "MY_DB_ID";
var results = await client.Databases.QueryAsync(databaseId, queryParams);
So the expected behavior was to get in results
all my pages with property "When" being set to some date in the future. However, it fetched me all pages in my Notion database.
I played around with it a bit to find out what is going on and found out that in RestClinent.PostAsync()
serialized filter looked like this:
{"Filter":{"equals":"0001-01-01T00:00:00","before":"0001-01-01T00:00:00","after":"0001-01-01T00:00:00","on_or_before":"0001-01-01T00:00:00","on_or_after":"2021-05-28T18:29:03.0391769+03:00","is_empty":true,"is_not_empty":true,"Property":"When"}}
It added to filter all possible filter states, that were not null
. This were all bool
s and DateTime
properties.
I updated DateFilter
so it would serialize only properties added to it manually:
public class DateFilter : SinglePropertyFilter
{
[JsonProperty("equals")]
[JsonConverter(typeof(IsoDateTimeConverter))]
public Nullable<DateTime> Equal { get; set; }
[JsonProperty("before")]
[JsonConverter(typeof(IsoDateTimeConverter))]
public Nullable<DateTime> Before { get; set; }
[JsonProperty("after")]
[JsonConverter(typeof(IsoDateTimeConverter))]
public Nullable<DateTime> After { get; set; }
[JsonProperty("on_or_before")]
[JsonConverter(typeof(IsoDateTimeConverter))]
public Nullable<DateTime> OnOrBefore { get; set; }
[JsonProperty("on_or_after")]
[JsonConverter(typeof(IsoDateTimeConverter))]
public Nullable<DateTime> OnOrAfter { get; set; }
[JsonProperty("past_week")]
public Dictionary<string, object> PastWeek { get; set; }
[JsonProperty("past_month")]
public Dictionary<string, object> PastMonth { get; set; }
[JsonProperty("past_year")]
public Dictionary<string, object> PastYear { get; set; }
[JsonProperty("next_week")]
public Dictionary<string, object> NextWeek { get; set; }
[JsonProperty("next_month")]
public Dictionary<string, object> NextMonth { get; set; }
[JsonProperty("next_year")]
public Dictionary<string, object> NextYear { get; set; }
[JsonProperty("is_empty")]
public Nullable<bool> IsEmpty { get; set; }
[JsonProperty("is_not_empty")]
public Nullable<bool> IsNotEmpty { get; set; }
}
Then it was serializing correctly, however I ran into another issue: filters have wrong format, not supported by Notion API (https://developers.notion.com/reference/post-database-query#post-database-query-filter). I received filter of the following format:
{
"Filter": {
"Property": "When",
"on_or_after": "2021-05-28T18:35:56.0871291+03:00",
}
}
While correct single property filter format according to docs would be:
{
"filter": {
"property": "When",
"date": {
"on_or_after": "2021-05-28T18:35:56.0871291+03:00"
}
}
}
@KoditkarVedant I could fix this issues and add a user friendly filter constructor, if you would accept my contributions.
This is a test issue to check the integration of issue label bot.
can we add issue label bot to automatically add labels to issues?
Endpoints
Note: Fill free to create a PR for each endpoint this is just a placeholder issue to know the missing test cases for endpoints.
DateProperty.cs
public class DateProperty : Property
{
public override PropertyType Type => PropertyType.MultiSelect;
public Dictionary<string, object> Date { get; set; }
}
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.