Package: pytion
pytionIndependent unofficialPython clientfor the officialNotion API(for internal integrations only)Client is built with its own object model based on APISo if you are usingnotion.soand want to automate some stuff with the original API, you're welcome!You can read any available data, create basic models, and even work with databases.Current Notion API version ="2022-06-28"*does not use notion-sdk-py clientSeeChange LogContentsQuick StartPytion APISearchingpytion.api.ElementModelspytion.modelsSupported Property typesSupported block typesBlock creating examplesBlock deletingDatabase operationsRetrievingAppending (creating a Page)Property ValuesLoggingQuick startpip install pytionCreate new integration and get your Notion API Token at notion.so ->here.Add your new integration 'manager' to your pages or databases by going to the page's menu (three dots), at the bottom of the menu clicking on Add connections and then searching for you new Integration.frompytionimportNotion;no=Notion(token=SOME_TOKEN)Or put your token for Notion API into filetokenat script directory and use simpleno = Notion()frompytionimportNotionno=Notion(token=SOME_TOKEN)page=no.pages.get("PAGE ID")# retrieve page data (not content) and create objectblocks=page.get_block_children()# retrieve page content and create list of objectsdatabase=no.databases.get("Database ID")# retrieve database data (not content) and create object# retrieve database content by filtering with sortingpages=database.db_filter(property_name="Done",property_type="checkbox",value=False,descending="title")In [1]: from pytion import Notion

In [2]: no = Notion(token=SOME_TOKEN)

In [3]: page = no.pages.get("a458613160da45fa96367c8a594297c7")
In [4]: print(page)
Notion/pages/Page(Example page)

In [5]: blocks = page.get_block_children_recursive()

In [6]: print(blocks)
Notion/blocks/BlockArray(## Migration planning [x] Rese)

In [7]: print(blocks.obj)
## Migration planning
[x] Reset new switch 2022-05-12T00:00:00.000+03:00 → 2022-05-13T01:00:00.000+03:00 
	- reboot
	- hold reset button
[x] Connect to console with baud rate 9600
[ ] Skip default configuration dialog
Use LinkTo(configs) 
[Integration changes](https://developers.notion.com/changelog?page=2)

In [8]: print(blocks.obj.simple)
Migration planning
Reset new switch 2022-05-12T00:00:00.000+03:00 → 2022-05-13T01:00:00.000+03:00 
	reboot
	hold reset button
Connect to console with baud rate 9600
Skip default configuration dialog
Use https://api.notion.com/v1/pages/90ea1231865f4af28055b855c2fba267 
https://developers.notion.com/changelog?page=2Pytion APIAlmost all operations are carried out throughNotionorElementobject:page=no.pages.get("a458613160da45fa96367c8a594297c7")# no -> Notion# pages -> URI in https://api.notion.com/v1/PAGES/a458613160da45fa96367c8a594297c7# get -> pytion API method# page -> Element# page.obj -> Page (main data structure)so:isinstance(no, Notion) == Trueisinstance(no.pages, Element) == Trueisinstance(no.databases, Element) == Trueisinstance(page, Element) == Trueisinstance(page.obj, Page) == Trueand if you want to retrieve a database - you must use"databases"URIdatabase=no.databases.get("123412341234")and the same applies for"blocks"and"users". Valid URI-s are:pagesblocksdatabasesusersWhen you work with existingElementobject likepageabove, allmethodsbelow will be applied to this Page:new_page=page.page_update(title="new page name 2")# new_page is not a new page, it is updated page# new_page.obj is equal page.obj except title and last_edited propertiesSearchThere is a search example:no=Notion(token)r=no.search("updating",object_type="page")print(r.obj)# output:# Page for updating# Page to updating databasespytion.api.ElementThere is a list of available methods for communicate withapi.notion.com. These methods are better structured innext chapter..get(id_)- Get Element by ID..get_parent(id_)- Get parent object of current object if possible..get_block_children(id_, limit)- Get children Block objects of current Block object (tabulated texts) if exist..get_block_children_recursive(id_, max_depth, limit, force)- Get children Block objects of current Block object (tabulated texts) if exist recursive..get_page_property(property_id, id_, limit)- Retrieve a page property item..get_page_properties(title_only, obj)- Retrieve the title or all properties of current Page or Pageobj(deprecated, useful for v1.3.0 only).db_query(id_, limit, filter_, sorts)- Query Database..db_filter(...see desc...)- Query Database..db_create(database_obj, parent, properties, title)- Create Database.There is no way to delete a database object yet!.db_update(id_, title, properties)- Update Database..page_create(page_obj, parent, properties, title)- Create Page..page_update(id_, properties, title, archived)- Update Page..block_update(id_, block_obj, new_text, archived)- Update text in Block..block_append(id_, block, blocks, after)- Append block or blocks children..get_myself()- Retrieve my bot User..from_linkto(linkto)- Creates new Element object based on LinkTo information..from_object(model)- Creates new Element object from Page, Block or Database object. Usable while Element object contains an Array.More details and usage examples of these methods you can see into func descriptions.Modelspytion.modelsThere are classesbased on APIstructures:RichTextbased onRich text objectRichTextArrayis a list of RichText objects with useful methodsYou can create object by simpleRichTextArray.create("My title text")and then use it in any methodsAny Rich Text getting from API will be RichTextArraystr()returns plain text of all "Rich Texts". ez+operator is available withstrandRichTextArray.simpleproperty returns stripped plain text without any markdown syntax. useful for URLDatabasebased onDatabase objectYou can create objectDatabase.create(...)and/or use.db_create(...)API methodattrs represent API model. Complex structures likecreated_byare wrapped in internal objectsuse.db_update()API method for modify a real database (for ex. properties or title)use.db_query()to get all database content (it will bePageArray)use.db_filter()to get database content with filtering and/or sortinghas.descriptionattrhas.is_inlineattr with the value True if the database appears in the page as an inline blockhas.public_urlattr when a page or database has been shared publiclyPagebased onPage objectYou can create objectPage.create(...)and/or use.page_create(...)API methoduse.page_update()method to modify attributes or delete the pageuse.get_block_children()to get page content (without nested blocks) (it will beBlockArray)use.get_block_children_recursive()to get page content with nested blocksuse.get_page_property()to retrieve the specificPropertyValueof the pagehas.public_urlattr when a page or database has been shared publiclyBlockbased onBlock objectYou can create objectBlock.create(...)of specific type fromsupport matrixbelow and then use it while creating pages or appendinguse.block_update()to replace content or changeextension attributesor delete the blockuse.block_append()to add a new block to a page or add a nested block to another blockuse.get_block_children()to get first level nested blocksuse.get_block_children_recursive()to get all levels nested blocksUserbased onUser objectYou can create objectUser.create(...)and use it in some properties likepeopletype propertyYou can retrieve more data about a User by his ID using.get()use.get_myself()to retrieve the current bot Userhas.workspace_nameattr forbottype usersPropertybased onProperty objectYou can create objectProperty.create(...)while creating or editing database:.db_create()or.db_update()formulatype properties configuration is not supportedPropertyValuebased onProperty valuesYou can create objectPropertyValue.create(...)to set or edit page properties by.page_create()or.page_update()files,formula,rolluptype properties are not editableThere are also usefulinternalclasses:BlockArrayis found when API returns page content in "list of blocks" formatit is useful to represent all content bystr()also it hassimpleproperty likeRichTextArrayobjectit automatically indentsstroutput of nested blocksPageArrayis found when API returns the result of database query (list of pages)LinkTois basic internal model to link to any Notion objectYou can create objectLinkTo.create()and use it in many places and methodsuseLinkTo(from_object=my_page1)to quickly create a link to any existing object of pytion.modelslinkproperty ofLinkToreturns expanded URLElementArrayis found while using.search()endpoint. It's a parent ofPageArrayAnd every model has a.get()method that returns API friendly JSON.Supported Property typesProperty typevalue typeread (DB)read value (Page)create (DB)create value (Page)Oper attrsConfig attrstitleRichTextArray++++rich_textRichTextArray++++numberint/float++++formatselectstr++++optionsmulti_selectList[str]++++optionsstatusstr++++****options,groups(read-only)datestr++++start: datetimeend: datetime*peopleList[User]++++**files+-+-checkboxbool++++urlstr++++emailstr++++phone_numberstr++++formula-+-n/arelationList[LinkTo]++++has_more: boolsingle_property/dual_propertyrollupdepends on relation and function+++n/ahas_more*****function,relation_property_id/relation_property_name,rollup_property_id/rollup_property_nameunique_idint+++n/aprefixcreated_time***datetime+++n/acreated_by***User+++n/alast_edited_time***datetime+++n/alast_edited_by***User+++n/a[*] - Create examples:pv = PropertyValue.create(type_="date", value=datetime.now())pv = PropertyValue.create(type_="date", date={"start": str(datetime(2022, 2, 1, 5)), "end": str(datetime.now())})[**] - Create example:user = User.create('1d393ffb5efd4d09adfc2cb6738e4812')pv = PropertyValue.create(type_="people", value=[user])[***] - Every Base model like Page already has mandatory attributes created/last_edited returned by API[****] - Status type is not configurable. API doesn't support NEW options added via Property modify or updating a Page[*****] - Notion API hasn'thas_moreattr. Only 25 references can be shown in the arrayMore details and examples can be found in DatabasesectionSupported block typesAt present the API only supports the block types which are listed in the reference below. Any unsupported block types will continue to appear in the structure, but only contain atypeset to"unsupported".
Colors are not yet supported.Every Block has mandatory attributes and extension attributes. There are mandatory:id: str- UUID-64 without hyphensobject: str- always"block"(from API)created_time: datetime- from APIcreated_by: User- from APIlast_edited_time: datetime- from APIlast_edited_by: User- from APItype: str- the type of block (from API)has_children: bool- does the block have children blocks (from API)archived: bool- does the block marked as deleted (from API)text: Union[str, RichTextArray]-main contentsimple: str- only simple text string (url expanded)Extension attributes are listed below in support matrix:Block TypeDescriptionRead supportCreate supportCan have childrenExtension attributesparagraphSimple Block with text+++heading_1Heading Block with text highest level++-/+*is_toggleable: boolheading_2Heading Block with text medium level++-/+*is_toggleable: boolheading_3Heading Block with text lowest level++-/+*is_toggleable: boolbulleted_list_itemText Block with bullet+-+numbered_list_itemText Block with number+-+to_doText Block with checkbox+++checked: booltoggleText Block with toggle to children blocks+-+codeText Block with code style+++language: str,caption: RichTextArraychild_pagePage inside+-+child_databaseDatabase inside+-+embedEmbed online content+--caption: RichTextArrayimageEmbed image content+--caption: RichTextArray,expiry_time: datetimevideoEmbed video content+--caption: RichTextArray,expiry_time: datetimefileEmbed file content+--caption: RichTextArray,expiry_time: datetimepdfEmbed pdf content+--caption: RichTextArray,expiry_time: datetimebookmarkBlock for URL Link+--caption: RichTextArraycalloutHighlighted footnote text Block+-+icon: dictquoteText Block with quote style+-+equationKaTeX compatible text Block+--dividerSimple line to divide the page+--table_of_contentsBlock with content structure in the page+--column--+column_list---link_previewSame asbookmark+--synced_blockBlock for synced content aka parent+-+synced_from: LinkTotemplateTemplate Block title+-+link_to_pageBlock with link to particular page@...+--link: LinkTotableTable Block with some attrs+-+table_width: inttable_rowChildren Blocks with table row content+--breadcrumbEmpty Block actually+--unsupportedBlocks unsupported by API+--[*] -heading_Xblocks can have children ifis_toggleableis TrueBlock creating examplesCreateparagraphblock object and add it to Notion:frompytion.modelsimportBlockmy_text_block=Block.create("Hello World!")my_text_block=Block.create(text="Hello World!",type_="paragraph")# the same# indented append my block to other known block:no.blocks.block_append("5f60073a9dda4a9c93a212a74a107359",block=my_text_block)# append my block to a known page (in the end)no.blocks.block_append("9796f2525016128d9af4bf12b236b555",block=my_text_block)# the same operation actually# another way to append:my_page=no.pages.get("9796f2525016128d9af4bf12b236b555")my_page.block_append(block=my_text_block)# insert block after the existing blockmy_page.block_append(block=my_text_block,after="1496f2525016128d9af4bf12b236b000")# ORexisting_block=no.blocks.get("1496f2525016128d9af4bf12b236b000")my_page.block_append(block=my_text_block,after=existing_block.obj)Createto_doblock object:frompytion.modelsimportBlockmy_todo_block=Block.create("create readme documentation",type_="to_do")my_todo_block2=Block.create("add 'create' method",type_="to_do",checked=True)Createcodeblock object:frompytion.modelsimportBlockmy_code_block=Block.create("code example here",type_="code",language="javascript")my_code_block2=Block.create("another code example",type_="code",caption="it will be plain text code block with caption")Createheadingblock object:frompytion.modelsimportBlockmy_code_block=Block.create("Title 1 example here",type_="heading_1")my_code_block2=Block.create("Toggle Title 2",type_="heading_2",is_toggleable=True)Block deletingno.blocks.block_update("3d4af9f0f98641dea8c44e3864eed4d0",archived=True)# or if you have Element with Block object alreadyblock.block_update(archived=True)Database operationsRetrievingfrompytionimportNotionno=Notion(token=TOKEN)# get all pages from Database# example 1pages=no.databases.db_query("114f1ef1f1241e2f12f41fe2f")# example 2db=no.databases.get("114f1ef1f1241e2f12f41fe2f")pages=db.db_query()print(pages.obj)# get pages with "task" in titlepages=db.db_filter("task")# get all pages with sorting by property name "Tags"pages=db.db_filter("",ascending="Tags")pages=db.db_filter("",descending="Tags")# get pages with Status property equals Donepages=db.db_filter(property_name="Status",property_type="status",value="Done")# get pages with Price property greater than 150pages=db.db_filter(property_name="Price",property_type="number",condition="greater_than",value=150)# complex querypages=db.db_filter(property_name="WorkTime",property_type="date",condition="before",value=datetime.now(),descending="Deadline",limit=25)pages=db.db_filter(property_name="last_edited_time",property_type="timestamp",condition="on_or_after",value="2023-10-03",limit=1)Queries with multifiltering and multisorting are not supportedBut you can compose your custom filter dict from API reference and calldb.db_filter(raw={...})Filter conditions and types combination ->Official API referenceAfter you gotpageswhich isElementobject, you can not call API methods directly onpagesbecause there isPageArrayobject with List of Pages.
If you need to change a Page or something else, you can follow these steps:# 1. create new Element object with non-list object typepage=no.pages.from_object(pages.obj[14])# choosed page from the PageArray# 2. call the desired API method on this pagepage.page_update(archived=True)# delete Page exampleAppending (creating a Page)There is a way to create a row into database. The same method is used to create any Page.
The difference is in only inparentargument which can be the Page or the Database.frompytion.modelsimportLinkTo# choose the parent target# it can be the databaseparent=LinkTo.create(database_id="043cb52491a44b80a5e5006237a4278f")# or the pageparent2=LinkTo.create(page_id="043cb52491a44b80a5e5006237a4278f")# if you need to set Propertiesfrompytion.modelsimportPropertyValueprops={"Tags":PropertyValue.create(type_="multi_select",value=["tag1","tag2"]),"done":PropertyValue.create("checkbox",True),"AnotherPropertyNAME":PropertyValue.create("date",datetime.now()),}# create the Pagepage=no.pages.page_create(parent=parent,title="Page 2")# without properties (will be empty)# orpage2=no.pages.page_create(parent=parent,properties=props,title="Page 2")# with propertiesProperty ValuesPytion Properties support table is describedaboveThere are Properties that are used in Database operations and PropertyValues that are used in Page operations.It's necessary to choose the type (type_arg) and value (value) of property.
The table shows the mapping between Property type and value (python) type.Thetype_must correspond to your database schema in Notion.fromdatetimeimportdatetimefrompytion.modelsimportPropertyValue,LinkTo,RichTextArray,User# + relation type:pv=PropertyValue.create("relation",value=[LinkTo.create(page_id="04262843082a478d97f741948a32613b")])# + people type:pv=PropertyValue.create(type_="people",value=[User.create('1d393ffb5efd4d09adfc2cb6738e4812')])# + date type:pv=PropertyValue.create(type_="date",value=datetime.now())pv=PropertyValue.create(type_="date",date={"start":str(datetime(2022,2,1,5)),"end":str(datetime.now())})# + rich_text (text or title field)pv=PropertyValue.create(type_="rich_text",value="Something Interesting")pv=PropertyValue.create("rich_text",value=RichTextArray.create("Something Interesting"))# + nubmerpv=PropertyValue.create(type_="number",value=156.2)# + statuspv=PropertyValue.create("status",value="Done")# update needed property valuesnew_props={"Tags":PropertyValue.create("multi_select",["tag2"]),"done":PropertyValue.create("checkbox",False),"when":PropertyValue.create("date",datetime.now()),}page=page_for_updates.page_update(properties=new_props)page=no.pages.page_update(page_for_updates_id,properties=new_props)# the same# rename (edit title property)page=page_for_updates.page_update(title=new_name)Pytion also provides the ways to change database schema - create/update/rename/delete properties:frompytion.modelsimportProperty# rename propertyproperties={"Name":Property.create(type_="title",name="Subject")}database=database_for_updates.db_update(properties=properties)database=no.databases.db_update(database_for_updates_id,properties=properties)# the same# change property typeproperties={"Tags":Property.create("select")}database=database_for_updates.db_update(properties=properties)# delete property from databaseproperties={"Old property name":Property.create(None)}database=database_for_updates.db_update(properties=properties)# rename databasedatabase=database_for_updates.db_update(title="Refactoring")LoggingLogging is muted by default. To enable to stdout and/or to file:frompytionimportsetup_loggingsetup_logging(level="debug",to_console=True,filename="pytion.log")