PK!FU>>simple_smartsheet/__init__.pyfrom .smartsheet import Smartsheet __all__ = ("Smartsheet",) PK!t<simple_smartsheet/config.pyfrom pathlib import Path from decouple import AutoConfig current_dir = Path() config = AutoConfig(current_dir) DEBUG = config("SIMPLE_SMARTSHEET_DEBUG", default=False, cast=bool) PK!J,,simple_smartsheet/constants.pyAPI_ROOT = "https://api.smartsheet.com/2.0" PK!":ܠsimple_smartsheet/exceptions.pyclass SmartsheetHTTPError(Exception): def __init__(self, http_response_code: int, error_code: int, message: str): result_msg = ( f"HTTP response code {http_response_code} - " f"Error code {error_code} - {message}" ) super().__init__(result_msg) self.message = message self.error_code = error_code self.http_response_code = http_response_code PK!+Z-$simple_smartsheet/models/__init__.pyfrom .cell import Cell from .column import Column from .row import Row from .sheet import Sheet from .extra import Result __all__ = ("Cell", "Column", "Row", "Sheet", "Result") PK!s+nn simple_smartsheet/models/base.pyimport logging import copy from datetime import datetime from typing import ( TypeVar, Dict, Any, Generic, Optional, cast, Type, List, Iterator, TYPE_CHECKING, Union, ClassVar, Sequence, ) import attr import marshmallow from cattr.converters import Converter from simple_smartsheet import utils from simple_smartsheet import config if TYPE_CHECKING: from simple_smartsheet.smartsheet import Smartsheet # noqa: F401 from simple_smartsheet.models.extra import Result logger = logging.getLogger(__name__) converter = Converter() converter.register_structure_hook(datetime, lambda ts, _: ts) converter.register_structure_hook(Union[float, str, datetime, None], lambda ts, _: ts) class Schema(marshmallow.Schema): class Meta: unknown = utils.get_unknown_field_handling(config.DEBUG) @marshmallow.post_dump def remove_none(self, data): return {key: value for key, value in data.items() if value is not None} class CoreSchema(Schema): pass T = TypeVar("T", bound="Object") @attr.s(auto_attribs=True, repr=False, kw_only=True) class Object: schema: ClassVar[Type[Schema]] = Schema @classmethod def load( cls: Type[T], data: Dict[str, Any], only: Optional[Sequence[str]] = None, exclude: Sequence[str] = (), ) -> T: schema = cls.schema(only=only, exclude=exclude) obj = converter.structure(schema.load(data), cls) return obj def dump( self, only: Optional[Sequence[str]] = None, exclude: Sequence[str] = () ) -> Dict[str, Any]: schema = self.schema(only=only, exclude=exclude) return schema.dump(self) def __repr__(self) -> str: if hasattr(self, "id") and hasattr(self, "name"): return f"{self.__class__.__qualname__}(name={self.name!r}, id={self.id!r})" elif hasattr(self, "id"): return f"{self.__class__.__qualname__}(id={self.id!r})" elif hasattr(self, "name"): return f"{self.__class__.__qualname__}(name={self.name!r})" else: return super().__repr__() def copy(self: T, deep: bool = True) -> T: if deep: return copy.deepcopy(self) else: return copy.copy(self) @attr.s(auto_attribs=True, repr=False, kw_only=True) class CoreObject(Object): schema: ClassVar[Type[CoreSchema]] = CoreSchema api: Optional["Smartsheet"] = attr.ib(default=None, init=False) TS = TypeVar("TS", bound=CoreObject) class CRUD(Generic[TS]): base_url = "" _get_url: Optional[str] = None _list_url: Optional[str] = None _update_url: Optional[str] = None _create_url: Optional[str] = None _delete_url: Optional[str] = None get_include_fields: Optional[Sequence[str]] = None get_exclude_fields: Sequence[str] = () list_include_fields: Optional[Sequence[str]] = None list_exclude_fields: Sequence[str] = () create_include_fields: Optional[Sequence[str]] = None create_exclude_fields: Sequence[str] = () update_include_fields: Optional[Sequence[str]] = None update_exclude_fields: Sequence[str] = () factory: Type[TS] = cast(Type[TS], CoreObject) def __init__(self, smartsheet: Optional["Smartsheet"]) -> None: self.api = smartsheet @property def get_url(self) -> str: return self._get_url or self.base_url + "/{id}" @property def list_url(self) -> str: return self._list_url or self.base_url @property def create_url(self) -> str: return self._create_url or self.base_url @property def update_url(self) -> str: return self._update_url or self.base_url + "/{id}" @property def delete_url(self) -> str: return self._delete_url or self.base_url + "/{id}" @property def id_to_obj(self) -> Dict[str, TS]: return {obj.id: obj for obj in self.list()} @property def name_to_obj(self) -> Dict[str, TS]: return {obj.name: obj for obj in self.list()} def get(self, name: Optional[str] = None, id: Optional[int] = None) -> TS: """Fetches a CoreObject by name or id. Args: name: name of the object id: id of the object Returns: CoreObject """ if name is None and id is None: raise ValueError(f"To use get method, either name or id must be provided") elif id: endpoint = self.get_url.format(id=id) obj_data = self.api.get(endpoint, path=None) logger.debug( "Creating an object %s from data: %s", repr(self.factory.__qualname__), str(obj_data), ) obj = self.factory.load( obj_data, self.get_include_fields, self.get_exclude_fields ) if not obj.id: obj.id = id obj.api = self.api return obj else: id_ = self.name_to_obj[name].id return self.get(id=id_) def list(self) -> List[TS]: """Fetches a list of CoreObject objects. Note: API usually returns an incomplete view of objects. For example: /sheets will return a list of sheets without columns or rows Args: name: name of the object id: id of the object Returns: CoreObject """ result = [] for obj_data in self.api.get(self.list_url, params={"includeAll": "true"}): logger.debug( "Creating an object '%s' from data: %s", self.factory.__qualname__, str(obj_data), ) obj = self.factory.load( obj_data, self.list_include_fields, self.list_exclude_fields ) obj.api = self.api result.append(obj) return result def create(self, obj: TS) -> "Result": """Creates CoreObject Args: obj: CoreObject Returns: Result object """ obj.api = self.api endpoint = self.create_url.format(obj=obj) return self.api.post( endpoint, obj.dump( only=self.create_include_fields, exclude=self.create_exclude_fields ), ) def update(self, obj: TS) -> "Result": """Updates CoreObject Args: obj: CoreObject Returns: Result object """ obj.api = self.api endpoint = self.update_url.format(id=obj.id) return self.api.put( endpoint, obj.dump( only=self.update_include_fields, exclude=self.update_exclude_fields ), ) def delete(self, name: Optional[str] = None, id: Optional[int] = None) -> "Result": """Deletes CoreObject by name or id Args: name: CoreObject name attribute id: CoreObject id attribute Returns: Result object """ if name is None and id is None: raise ValueError( f"To use delete method, either name or id must be provided" ) elif id: endpoint = self.delete_url.format(id=id) return self.api.delete(endpoint) else: id_ = self.name_to_obj[name].id return self.delete(id=id_) def __iter__(self) -> Iterator[TS]: return iter(self.list()) PK!ܷ simple_smartsheet/models/cell.pyfrom datetime import datetime from typing import Optional, Union, ClassVar, Type, Any, List import attr from marshmallow import fields from simple_smartsheet.models.base import Schema, Object from simple_smartsheet.models.extra import Hyperlink, HyperlinkSchema class CellSchema(Schema): column_id = fields.Int(data_key="columnId") column_type = fields.Str(data_key="columnType") conditional_format = fields.Str(data_key="conditionalFormat") display_value = fields.Str(data_key="displayValue") format = fields.Str() formula = fields.Str() hyperlink = fields.Nested(HyperlinkSchema) image = fields.Field() # TODO: Image object link_in_from_cell = fields.Field(data_key="linkInFromCell") # TODO: CellLink object link_out_to_cells = fields.List( fields.Field(), data_key="linksOutToCells" ) # TODO: CellLink object object_value = fields.Field(data_key="objectValue") # TODO: ObjectValue object override_validation = fields.Bool(data_key="overrideValidation") strict = fields.Bool() value = fields.Field() @attr.s(auto_attribs=True, repr=False, kw_only=True) class Cell(Object): column_id: Optional[int] = None column_type: Optional[str] = None conditional_format: Optional[str] = None display_value: Optional[str] = None format: Optional[str] = None formula: Optional[str] = None hyperlink: Optional[Hyperlink] = None image: Optional[Any] = None # TODO: Image object link_in_from_cell: Optional[Any] = None # TODO: CellLink object link_out_to_cells: Optional[List[Any]] = None # TODO: CellLink object object_value: Optional[Any] = None # TODO: ObjectValue object override_validation: Optional[bool] = None strict: bool = True value: Union[float, str, datetime, None] = None schema: ClassVar[Type[CellSchema]] = CellSchema def __repr__(self) -> str: return ( f"{self.__class__.__qualname__}(column_id={self.column_id!r}, " f"value={self.value!r})" ) PK! "simple_smartsheet/models/column.pyfrom typing import Optional, List import attr from marshmallow import fields from simple_smartsheet.models.base import Schema, Object from simple_smartsheet.models.extra import AutoNumberFormatSchema, AutoNumberFormat class ContactOptionSchema(Schema): email = fields.Str() name = fields.Str() @attr.s(auto_attribs=True, kw_only=True) class ContactOption(Object): email: str name: str class ColumnSchema(Schema): id = fields.Int() system_column_type = fields.Str(data_key="systemColumnType") type = fields.Str() # TODO: should be enum auto_number_format = fields.Nested( AutoNumberFormatSchema, data_key="autoNumberFormat" ) contact_options = fields.Nested(ContactOptionSchema, data_key="contactOptions") format = fields.Str() hidden = fields.Bool() index = fields.Int() locked = fields.Bool() locked_for_user = fields.Bool(data_key="lockedForUser") options = fields.Str(many=True) primary = fields.Bool() symbol = fields.Str() tags = fields.Str(many=True) title = fields.Str() validation = fields.Bool() version = fields.Int() width = fields.Int() @attr.s(auto_attribs=True, repr=False, kw_only=True) class Column(Object): id: Optional[int] = None system_column_type: Optional[str] = None type: Optional[str] = None auto_number_format: Optional[AutoNumberFormat] = None contact_options: Optional[ContactOption] = None format: Optional[str] = None hidden: Optional[bool] = None index: Optional[int] = None locked: Optional[bool] = None locked_for_user: Optional[bool] = None options: Optional[List[str]] = None primary: Optional[bool] = None symbol: Optional[str] = None tags: Optional[List[str]] = None title: Optional[str] = None validation: Optional[bool] = None version: Optional[int] = None width: Optional[int] = None def __repr__(self) -> str: return ( f"{self.__class__.__qualname__}(id={self.id!r}, " f"title={self.title!r})" ) PK!k55!simple_smartsheet/models/extra.pyfrom typing import Optional, ClassVar, Type, List, Any import attr from marshmallow import fields from simple_smartsheet.models.base import Schema, Object class AutoNumberFormatSchema(Schema): fill = fields.Str() prefix = fields.Str() starting_num = fields.Str(data_key="startingNumber") suffix = fields.Str() @attr.s(auto_attribs=True, kw_only=True) class AutoNumberFormat(Object): fill: str prefix: str starting_num: int suffix: str class ErrorSchema(Schema): error_code = fields.Int(data_key="errorCode") message = fields.Str() ref_id = fields.Str(data_key="refId") detail = fields.Field() @attr.s(auto_attribs=True, kw_only=True) class Error(Object): error_code: int message: str ref_id: str detail: Optional[Any] = None schema: ClassVar[Type[ErrorSchema]] = ErrorSchema class ResultSchema(Schema): failed_items = fields.List(fields.Field(), data_key="failedItems") message = fields.Str() result = fields.Field() result_code = fields.Int(data_key="resultCode") version = fields.Int() @attr.s(auto_attribs=True, kw_only=True) class Result(Object): failed_items: List[Any] = attr.Factory(list) message: Optional[str] = None result: Optional[Any] = None result_code: Optional[int] = None version: Optional[int] = None schema: ClassVar[Type[ResultSchema]] = ResultSchema class HyperlinkSchema(Schema): report_id = fields.Int(data_key="reportId") sheet_id = fields.Int(data_key="sheetId") sight_id = fields.Int(data_key="sightId") url = fields.Str() @attr.s(auto_attribs=True, kw_only=True) class Hyperlink(Object): url: str report_id: Optional[int] = None sheet_id: Optional[int] = None sight_id: Optional[int] = None schema: ClassVar[Type[HyperlinkSchema]] = HyperlinkSchema PK!bsimple_smartsheet/models/row.pyfrom typing import Optional, List, TYPE_CHECKING, Dict, Any, ClassVar, Type import attr from marshmallow import fields from datetime import datetime from simple_smartsheet.models.base import Schema, Object from simple_smartsheet.models.cell import Cell, CellSchema from simple_smartsheet.models.column import Column, ColumnSchema if TYPE_CHECKING: from simple_smartsheet.models.sheet import Sheet class RowSchema(Schema): id = fields.Int() sheet_id = fields.Int(data_key="sheetId") access_level = fields.Str(data_key="accessLevel") attachments = fields.List(fields.Field()) # TODO: Attachment object cells = fields.Nested(CellSchema, many=True) columns = fields.Nested(ColumnSchema, many=True) conditional_format = fields.Str(data_key="conditionalFormat") created_at = fields.DateTime(data_key="createdAt") created_by = fields.Field(data_key="createdBy") # TODO: User object discussions = fields.List(fields.Field()) # TODO: Discussion object expanded = fields.Bool() filtered_out = fields.Bool(data_key="filteredOut") format = fields.Str() in_critical_path = fields.Bool(data_key="inCriticalPath") locked = fields.Bool() locked_for_user = fields.Bool(data_key="lockedForUser") modified_at = fields.DateTime(data_key="modifiedAt") modified_by = fields.Field(data_key="modifiedBy") # TODO: User object num = fields.Int(data_key="rowNumber") permalink = fields.Str() version = fields.Int() # location-specifier attributes parent_id = fields.Int(data_key="parentId") sibling_id = fields.Int(data_key="siblingId") above = fields.Bool() indent = fields.Int() outdent = fields.Int() to_bottom = fields.Bool(data_key="toBottom") to_top = fields.Bool(data_key="toTop") @attr.s(auto_attribs=True, repr=False, kw_only=True) class Row(Object): id: Optional[int] = None sheet_id: Optional[int] = None access_level: Optional[str] = None attachments: List[Any] = attr.Factory(list) cells: List[Cell] = attr.Factory(list) columns: List[Column] = attr.Factory(list) conditional_format: Optional[str] = None created_at: Optional[datetime] = None created_by: Optional[Any] = None discussions: List[Any] = attr.Factory(list) expanded: Optional[bool] = None filtered_out: Optional[bool] = None format: Optional[str] = None in_critical_path: Optional[bool] = None locked: Optional[bool] = None locked_for_user: Optional[bool] = None modified_at: Optional[datetime] = None modified_by: Optional[Any] = None num: Optional[int] = None permalink: Optional[str] = None version: Optional[int] = None # location-specified attributes parent_id: Optional[int] = None sibling_id: Optional[int] = None above: Optional[bool] = None indent: Optional[int] = None outdent: Optional[int] = None to_bottom: Optional[bool] = None to_top: Optional[bool] = None # index column_title_to_cell: Dict[str, Cell] = attr.Factory(dict) column_id_to_cell: Dict[int, Cell] = attr.Factory(dict) schema: ClassVar[Type[RowSchema]] = RowSchema def __repr__(self) -> str: return f"{self.__class__.__qualname__}(id={self.id!r}, " f"num={self.num!r})" def update_index(self, sheet: "Sheet") -> None: self.column_title_to_cell.clear() self.column_id_to_cell.clear() for cell in self.cells: column_id = cell.column_id if column_id is None: continue column_title = sheet.column_id_to_column[column_id].title if column_title is None: continue self.column_id_to_cell[column_id] = cell self.column_title_to_cell[column_title] = cell def get_cell( self, column_title: Optional[str] = None, column_id: Optional[int] = None ) -> Optional[Cell]: if column_title is not None: return self.column_title_to_cell.get(column_title) elif column_id is not None: return self.column_id_to_cell.get(column_id) else: raise ValueError( "Either column_title or column_id argument should be provided" ) PK!d,,!simple_smartsheet/models/sheet.pyimport logging from datetime import datetime from typing import Optional, Dict, List, ClassVar, Type, Sequence import attr from marshmallow import fields from simple_smartsheet.models.base import Schema, CoreSchema, Object, CoreObject, CRUD from simple_smartsheet.models.column import Column, ColumnSchema from simple_smartsheet.models.row import Row, RowSchema from simple_smartsheet.models.extra import Result logger = logging.getLogger(__name__) class UserSettingsSchema(Schema): critical_path_enabled = fields.Bool(data_key="criticalPathEnabled") display_summary_tasks = fields.Bool(data_key="displaySummaryTasks") @attr.s(auto_attribs=True, repr=False, kw_only=True) class UserSettings(Object): critical_path_enabled: bool display_summary_tasks: bool class SheetSchema(CoreSchema): """Marshmallow Schema for Smartsheet Sheet object Additional details about fields can be found here: http://smartsheet-platform.github.io/api-docs/#sheets """ id = fields.Int() name = fields.Str() access_level = fields.Str(data_key="accessLevel") permalink = fields.Str() favorite = fields.Bool() created_at = fields.DateTime(data_key="createdAt") modified_at = fields.DateTime(data_key="modifiedAt") version = fields.Int() total_row_count = fields.Int(data_key="totalRowCount") effective_attachment_options = fields.List( fields.Str(), data_key="effectiveAttachmentOptions" ) gantt_enabled = fields.Bool(data_key="ganttEnabled") dependencies_enabled = fields.Bool(data_key="dependenciesEnabled") resource_management_enabled = fields.Bool(data_key="resourceManagementEnabled") cell_image_upload_enabled = fields.Bool(data_key="cellImageUploadEnabled") user_settings = fields.Nested(UserSettingsSchema, data_key="userSettings") columns = fields.Nested(ColumnSchema, many=True) rows = fields.Nested(RowSchema, many=True) @attr.s(auto_attribs=True, repr=False, kw_only=True) class Sheet(CoreObject): """Represent Smartsheet Sheet object Additional details about fields can be found here: http://smartsheet-platform.github.io/api-docs/#sheets Extra attributes: row_rum_to_row: mapping of row number to Row object row_id_to_row: mapping of row id to Row object column_title_to_column: mapping of column title to Column object column_id_to_column: mapping of column id to Column object schema: reference to SheetSchema """ name: str id: Optional[int] = None access_level: Optional[str] = None permalink: Optional[str] = None favorite: Optional[bool] = None created_at: Optional[datetime] = None modified_at: Optional[datetime] = None version: Optional[int] = None total_row_count: Optional[int] = None effective_attachment_options: List[str] = attr.Factory(list) gantt_enabled: Optional[bool] = None dependencies_enabled: Optional[bool] = None resource_management_enabled: Optional[bool] = None cell_image_upload_enabled: Optional[bool] = None user_settings: Optional[UserSettings] = None columns: List[Column] = attr.Factory(list) rows: List[Row] = attr.Factory(list) row_num_to_row: Dict[int, Row] = attr.Factory(dict) row_id_to_row: Dict[int, Row] = attr.Factory(dict) column_title_to_column: Dict[str, Column] = attr.Factory(dict) column_id_to_column: Dict[int, Column] = attr.Factory(dict) schema: ClassVar[Type[SheetSchema]] = SheetSchema def __attrs_post_init__(self) -> None: self.update_index() def update_index(self) -> None: """Updates columns and row indices for quick lookup""" self.update_column_index() self.update_row_index() def update_column_index(self) -> None: """Updates columns index for quick lookup by title and ID""" self.column_title_to_column.clear() self.column_id_to_column.clear() for column in self.columns: if column.id is None: continue self.column_id_to_column[column.id] = column column_title = column.title if column_title is None: continue if column_title in self.column_title_to_column: logger.info( "Column with the title %s is already present in the index" % column_title ) self.column_title_to_column[column_title] = column def update_row_index(self) -> None: """Updates row index for quick lookup by row number and ID""" self.row_num_to_row.clear() self.row_id_to_row.clear() for row in self.rows: self.row_num_to_row[row.num] = row self.row_id_to_row[row.id] = row row.update_index(self) def get_row( self, row_num: Optional[int] = None, row_id: Optional[int] = None ) -> Optional[Row]: """Returns Row object by row number or ID Either row_num or row_id must be provided Args: row_num: row number row_id: row id Returns: Row object """ if row_num is not None: return self.row_num_to_row.get(row_num) elif row_id is not None: return self.row_id_to_row.get(row_id) else: raise ValueError("Either row_num or row_id argument should be provided") def get_column( self, column_title: Optional[str] = None, column_id: Optional[int] = None ) -> Optional[Column]: """Returns Column object by column title or ID Either column_title or column_id must be provided Args: column_title: column title (case-sensitive) column_id: column id Returns: Column object """ if column_title is not None: return self.column_title_to_column.get(column_title) elif column_id is not None: return self.column_id_to_column.get(column_id) else: raise ValueError( "Either column_title or column_id argument should be provided" ) def add_rows(self, rows: Sequence[Row]) -> Result: """Adds several rows to the smartsheet. Sheet must have api attribute set. It is automatically set when method Smartsheet.sheets.get() is used Every row must have either location-specifier attributes or row number set More details: http://smartsheet-platform.github.io/api-docs/#add-rows Args: rows: sequence of Row objects Returns: Result object """ if self.api is None: raise ValueError("To use this method, api attribute must be set") include_fields = ( "parent_id", "sibling_id", "above", "indent", "outdent", "to_bottom", "to_top", "expanded", "format", "cells.column_id", "cells.formula", "cells.value", "cells.hyperlink", "cells.link_in_from_cell", "cells.strict", "cells.format", "cells.image", "cells.override_validation", "locked", ) data = [] schema = RowSchema(only=include_fields) for row in rows: new_row = row.copy(deep=False) new_row.cells = [ cell for cell in row.cells if cell.value is not None or cell.formula is not None ] data.append(schema.dump(new_row)) return self.api.post(f"/sheets/{self.id}/rows", data=data) def add_row(self, row: Row) -> Result: """Adds a single row to the smartsheet. Sheet must have api attribute set. It is automatically set when method Smartsheet.sheets.get() is used A row must have either location-specifier attributes or row number set More details: http://smartsheet-platform.github.io/api-docs/#add-rows Args: row: Row object Returns: Result object """ return self.add_rows([row]) def update_rows(self, rows: Sequence[Row]) -> Result: """Updates several rows in the Sheet. Sheet must have api attribute set. It is automatically set when method Smartsheet.sheets.get() is used More details: http://smartsheet-platform.github.io/api-docs/#update-rows Args: rows: sequence of Row objects Returns: Result object """ if self.api is None: raise ValueError("To use this method, api attribute must be set") include_fields = ( "id", "parent_id", "sibling_id", "above", "indent", "outdent", "to_bottom", "to_top", "expanded", "format", "cells.column_id", "cells.formula", "cells.value", "cells.hyperlink", "cells.link_in_from_cell", "cells.strict", "cells.format", "cells.image", "cells.override_validation", "locked", ) data = [] schema = RowSchema(only=include_fields) for row in rows: new_row = row.copy(deep=False) new_row.cells = [ cell for cell in row.cells if cell.value is not None or cell.formula is not None ] data.append(schema.dump(new_row)) return self.api.put(f"/sheets/{self.id}/rows", data=data) def update_row(self, row: Row) -> Result: """Updates a single row in the Sheet. Sheet must have api attribute set. It is automatically set when method Smartsheet.sheets.get() is used More details: http://smartsheet-platform.github.io/api-docs/#update-rows Args: row: Row object Returns: Result object """ return self.update_rows([row]) def delete_rows(self, row_ids: Sequence[int]) -> Result: """Deletes several rows in the Sheet. Rows are identified by ids. Args: row_ids: sequence of row ids Returns: Result object """ if self.api is None: raise ValueError("To use this method, api attribute must be set") endpoint = f"/sheets/{self.id}/rows" params = {"ids": ",".join(str(row_id) for row_id in row_ids)} return self.api.delete(endpoint, params=params) def delete_row(self, row_id: int) -> Result: """Deletes a single row in the Sheet specified by ID. Args: row_id: Row id Returns: Result object """ return self.delete_rows([row_id]) class SheetsCRUD(CRUD[Sheet]): base_url = "/sheets" factory = Sheet default_path = "sheets" create_include_fields = ( "name", "columns.primary", "columns.title", "columns.type", "columns.auto_number_format", "columns.options", "columns.symbol", "columns.system_column_type", "columns.width", ) PK!BBsimple_smartsheet/smartsheet.pyfrom typing import Optional, Dict, Any import requests from simple_smartsheet.constants import API_ROOT from simple_smartsheet.models.sheet import SheetsCRUD from simple_smartsheet.models.extra import Error, Result from simple_smartsheet.exceptions import SmartsheetHTTPError from simple_smartsheet.types import JSONType class Smartsheet: """Smartsheet API class that provides a way to interact with Smartsheet objects. Attributes: token: Smartsheet API token, obtained in Personal Settings -> API access session: requests.Session object which stores headers based on the token sheets: SheetsCRUD object which provides methods to interact with Sheets """ API_HEADERS = {"Content-Type": "application/json", "Accept": "application/json"} def __init__(self, token: str) -> None: self.session = requests.Session() self.token = token self.sheets = SheetsCRUD(self) @property def token(self) -> str: return self._token @token.setter def token(self, value) -> None: self._token = value # when the token is changed, update headers too self._update_headers() def _update_headers(self) -> None: """Updates HTTP Headers with the token""" headers = {"Authorization": f"Bearer {self.token}", **self.API_HEADERS} self.session.headers.update(headers) @staticmethod def get_endpoint_url(endpoint: str) -> str: """Build a full API url based on the relative API path. For example: get_endpoint_url('/sheets') -> 'https://api.smartsheet.com/2.0/sheets' """ return API_ROOT + endpoint def get( self, endpoint: str, path: Optional[str] = "data", params: Optional[Dict[str, Any]] = None, ) -> JSONType: """Performs HTTP GET on the endpoint Args: endpoint: relative API endpoint, for example '/sheets' path: in response extract the data under the specific path. Default - 'data'. Specify None if not needed params: HTTP query params dictionary Returns: JSON data from the response, under the specific key. """ url = self.get_endpoint_url(endpoint) response = self.session.get(url, params=params) if response.status_code >= 400: error = Error.load(response.json()) raise SmartsheetHTTPError( response.status_code, error.error_code, error.message ) else: response_json = response.json() if path: if path in response_json: return response_json[path] raise AttributeError( f"Response from GET {url} does not contain key {path!r}" ) else: return response_json def post(self, endpoint: str, data: Optional[JSONType] = None) -> Optional[Result]: """Performs HTTP POST on the endpoint Args: endpoint: relative API endpoint, for example '/sheets' data: dictionary or list with data that is going to be sent as JSON Returns: Result object """ url = self.get_endpoint_url(endpoint) if data: response = self.session.post(url, json=data) else: response = self.session.post(url) if response.status_code >= 400: error = Error.load(response.json()) raise SmartsheetHTTPError( response.status_code, error.error_code, error.message ) elif response.status_code != 204: return Result.load(response.json()) else: return None def put(self, endpoint: str, data: JSONType) -> Optional[Result]: """Performs HTTP PUT on the endpoint Args: endpoint: relative API endpoint, for example '/sheets' data: dictionary or list with data that is going to be sent as JSON Returns: Result object """ url = self.get_endpoint_url(endpoint) response = self.session.put(url, json=data) if response.status_code >= 400: error = Error.load(response.json()) raise SmartsheetHTTPError( response.status_code, error.error_code, error.message ) else: return Result.load(response.json()) def delete(self, endpoint: str, params: Optional[Dict[str, Any]] = None) -> Result: """Performs HTTP DELETE on the endpoint Args: endpoint: relative API endpoint, for example '/sheets' params: HTTP query params dictionary Returns: Result object """ url = self.get_endpoint_url(endpoint) response = self.session.delete(url, params=params) if response.status_code >= 400: error = Error.load(response.json()) raise SmartsheetHTTPError( response.status_code, error.error_code, error.message ) else: return Result.load(response.json()) PK!.\Ubbsimple_smartsheet/types.pyfrom typing import Union, Dict, List, Any JSONType = Union[Dict[str, Any], List[Dict[str, Any]]] PK!jOsimple_smartsheet/utils.pyfrom marshmallow import EXCLUDE, RAISE def get_unknown_field_handling(debug: bool) -> str: if debug: return RAISE else: return EXCLUDE PK!H]--)simple_smartsheet-0.1.0.dist-info/LICENSEMIT License Copyright (c) 2018 Dmitry Figol Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. PK!HUV'simple_smartsheet-0.1.0.dist-info/WHEEL A н#Z;/"d&F[xzw@Zpy3Fv]O"J˫( }$%PK!Hg&Dd*simple_smartsheet-0.1.0.dist-info/METADATAN0@9XYP(M%;CտgU@/<7^‹␰8 Եia+}p"(X4r/MJ.- ʣ%.x4ZTm4}L̰rt<~ ,\0jRq(0 ~6Ң wmpSvMc+\I$ݾpummLUOVh-&*j)/nNݎŠ߲Ӌ,dG(bhJ-2NA˸]sxUިFljsж04vxZ y&7PK!HrAG(simple_smartsheet-0.1.0.dist-info/RECORDɮH}{C1f f4A1.2~j%H!Q6ߑΏm6#Fߋv"ЩX/1Kpbۨ=,00ع{: ~1;v!ZJVç|aadЭ4=!vIŅI&zD0Z@e{םdl'9IߦM&#fm0ꑃOˑ+ӣ1 aɎPLT~cq0|u`CsEkhy3sԨ Aʪw`W%jN,(a/zX\vMǶ^QauYzp\KX|JxBi#iZ>b^Gv9CPu :?x[:YJgs~N7$clcٵQBŝ WI ?@Ex{7v=х6<υ[;[OR5w")b䟗I0~YȚ3:vcu}XCޥvy'vӘ\I&wo8qj< Q󴴽RI@V\}f$5]L wJlWލ3tWL%ue8~ڄ^`JaqMHt x$W.JNMmKҿ[p+~<@?`wۦ3 m8Cp`"?eat;ÙΖr/ 1F7b 9$ U[8~~@'1+lrFBԇ<$z'Ck;j$)R$ hOOC̦{]ג%7J&&˜|+i@g8~IT 7PK!FU>>simple_smartsheet/__init__.pyPK!t<ysimple_smartsheet/config.pyPK!J,,isimple_smartsheet/constants.pyPK!":ܠsimple_smartsheet/exceptions.pyPK!+Z-$simple_smartsheet/models/__init__.pyPK!s+nn simple_smartsheet/models/base.pyPK!ܷ P"simple_smartsheet/models/cell.pyPK! "*simple_smartsheet/models/column.pyPK!k55!2simple_smartsheet/models/extra.pyPK!b9:simple_smartsheet/models/row.pyPK!d,,!Ksimple_smartsheet/models/sheet.pyPK!BBwsimple_smartsheet/smartsheet.pyPK!.\UbbEsimple_smartsheet/types.pyPK!jOߌsimple_smartsheet/utils.pyPK!H]--)simple_smartsheet-0.1.0.dist-info/LICENSEPK!HUV',simple_smartsheet-0.1.0.dist-info/WHEELPK!Hg&Dd*ƒsimple_smartsheet-0.1.0.dist-info/METADATAPK!HrAG(Rsimple_smartsheet-0.1.0.dist-info/RECORDPKߗ