Package: ckanext-flakes
ckanext-flakesTools for creating and managing independent chunks of data.This extension provides a base entity for storing arbitrary data. It can be
used in a number of cases, especially, if you don't want yet to create a brand
new model, database migrations and tables, but you have no other options.ckanext-flakesgives you a set of actions for creating and managing small
dictionary-like objects(anything, that can be serialized into JSON). If you are
using it and want to add an extra action, feel free to create a PR or an issue
with your suggestion.StructureExamplesExample pluginsRequirementsInstallationConfigurationInterfacesAPIflakes_flake_createflakes_flake_showflakes_flake_listflakes_flake_updateflakes_flake_overrideflakes_flake_deleteflakes_flake_lookupflakes_flake_validateflakes_data_validateflakes_data_exampleflakes_flake_materializeflakes_flake_combineflakes_flake_mergeflakes_data_patchflakes_extras_patchExamplesCreate a collection of recordsScenario: user needs a todo listFlakes created by any user are visible only to this user so flakes can be used
as a storage for private data.Flakes can haveextras, that plays a role of tags.extrasrepresented by a
dictionary and whenever user lists his flakes, he has an option to see only
flakes that contains particular data inside extras.flake_create=tk.get_action("flakes_flake_create")flake_list=tk.get_action("flakes_flake_create")# create an urgent taksflake_create({"user":"john"},{"data":{"task":"feed the cat"},"extras":{"when":"today","type":"task"}})# create a couple of tasks that can waitflake_create({"user":"john"},{"data":{"task":"buy food"},"extras":{"when":"tomorrow","type":"task"}})flake_create({"user":"john"},{"data":{"task":"update documentation"},"extras":{"when":"tomorrow","type":"task"}})# list all the tasksflake_list({"user":"john"},{"extras":{"type":"task"}})# list all the urgent tasksflake_list({"user":"john"},{"extras":{"type":"task","when":"today"}})# list all the tasks for tomorrowflake_list({"user":"john"},{"extras":{"type":"task","when":"tomorrow"}})Save the value of the option individually for every userScenario: each user can set a theme of application and this theme will be applied only for the current userFlakes are created for the user from thecontext. Flakes of the user A are
visible only to the user A, flakes of the user B exist in the different
namespace and are visible only to the user B.Each flakecanhave a name. Name must be unique among the flakes of the
user. But different users can use the same names for their flakes, because
every user has its own namespace for flakes.Flakes can be created either viaflakes_flake_createaction(acceptsoptionalname and raises exception if name is not unique) orflakes_flake_override(requires a name and creates a new flake if name is not
taken or updates existing flake if name already used by some flake)In order to get the flake useflakes_flake_showwith theidof the flake orflakes_flake_lookupwith thename.# set a theme for Johntk.get_action("flakes_flake_override")({"user":"john"},{"name":"application:theme","data":{"theme":"dark"}})# set a theme for Marytk.get_action("flakes_flake_override")({"user":"mary"},{"name":"application:theme","data":{"theme":"light"}})# get the value from the flakejohn_theme=tk.get_action("flakes_flake_lookup")({"user":"john"},{"name":"application:theme"})["data"]["theme"]mary_theme=tk.get_action("flakes_flake_lookup")({"user":"mary"},{"name":"application:theme"})["data"]["theme"]assertjohn_theme=="dark"assertmary_theme=="light"Create and obtain global variableScenario: application requires global option, that can be changed in runtimeBy default flakes are created in the "namespace" of the current user. Only the
author can see and modify his own flakes.Global values should not be owned by someone, so here we need "unowned" flake -
the flake that is not connected to the particular user. Only sysadmin can
create such flakes, so we are going to useignore_auth=Trueattribute of the
context.We'll useflakes_flake_overrideaction, that accepts anameof the flake
and either updates existing flakes with this name or creates a new one if this
name is free. In this way we'll avoid duplicates of the global flake.# create a flaketk.get_action("flakes_flake_override")({"ignore_auth":True},# only syadmin allowed to create unowned flakes with empty author id{"name":"global:config:value","data":{"value":1},"author_id":None})# get the value from the flakevalue=tk.get_action("flakes_flake_lookup")({"ignore_auth":True},{"name":"global:config:value","author_id":None})["data"]["value"]Example pluginsThese plugins implement basic features that can be used as a real-life example
ofckanext-flakesusage.flakes_ratingUser can rate a package via API action. Addckanext.flakes_rating.show_package_widget = trueto the config and default
widget will be added to the sidebar ondataset.readpage.flakes_feedbackUser can leave a feedback for package via API action. Addckanext.flakes_feedback.enable_views = trueto the config and default page
will be added to navigation tabs ondataset.readpage.RequirementsRequires python v3.7 or greater. Python v2 support doesn't require much effort,
but it neither worth the time you'll spend on it.Compatibility with core CKAN versions:CKAN versionCompatible?2.9yes2.10yesInstallationTo install ckanext-flakes:Install it viapip:pipinstallckanext-flakesAddflakesto theckan.pluginssetting in your CKAN config file.Run DB migrations:ckandbupgrade-pflakesConfiguration# Any user can create a new flake.# (optional, default: true)ckanext.flakes.creation.allowed=false# Any user can validate flake or plain data.# (optional, default: false)ckanext.flakes.validation.allowed=trueInterfacesProvidesckanext.flakes.interfaces.IFlakesinterface. Always useinherit=Truewhen implementing it, because it may change in the future.Currently it provides the following hooks:classIFlakes(Interface):"""Extend functionality of ckanext-flakes"""defget_flake_schemas(self)->dict[str,dict[str,Any]]:"""Register named validation schemas.Used by `flakes_flake_validate` and `flakes_data_validate` actions.Returns:Mapping of names and corresponding validation schemas.Example:def get_flake_schemas(self) -> dict[str, dict[str, Any]]:return {"schema-that-requires-name": {"name": [not_missing]}}"""return{}defget_flake_factories(self)->dict[str,Callable[[dict[str,Any]],dict[str,Any]]]:"""Register named example factories.Used by `flakes_data_example` action.Returns:Mapping of names and corresponding example factories.Example:def get_flake_factories(self) -> dict[str, dict[str, Any]]:def factory(payload: dict[str, Any]):return {"field": "value"}return {"test-factory": factory}"""return{}APIflakes_flake_createCreate a flake.Args:name (str, optional): name of the flake
data (dict): flake's data
parent_id (str, optional): ID of flake to extend
author_id (str, optional): author ID(can be set only by sysadmin)
extras (dict): flake's extra detailsflakes_flake_showDisplay existing flakeArgs:id (str): ID of flake to display
expand (bool, optional): Extend flake using data from the parent flakesflakes_flake_listDisplay all flakes of the user.Ifextrasdictionary passed, show only flakes that contains given extras. Example:first_flake = Flake(extras={"xxx": {"yyy": "hello"}})
second_flake = Flake(extras={"xxx": {"yyy": "world"}})

flake_list(context, {"extras": {"xxx": {"yyy": "hello"}})
>>> first_flakeArgs:expand (bool, optional): Extend flake using data from the parent flakes
extras (dict, optional): Show only flakes whose extras contains passed dict
author_id (str, optional): author ID(can be set only by sysadmin)flakes_flake_updateUpdate existing flakeArgs:id (str): ID of flake to update
data (dict): flake's data
parent_id (str, optional): ID of flake to extend
extras (dict): flake's extra detailsflakes_flake_overrideUpdate existing flake by name or create a new one.Args:name (str): Name flake to override
data (dict): template itself
parent_id (str, optional): ID of flake to extend
author_id (str, optional): author ID(can be set only by sysadmin if flake does not exist)
extras (dict): flake's extra detailsflakes_flake_deleteDelete existing flakeArgs:id (str): ID of flake to deleteflakes_flake_lookupDisplay flake using its name.Args:name (str): Name of the flake
expand (bool, optional): Extend flake using data from the parent flakes
author_id (str, optional): author ID(can be set only by sysadmin)flakes_flake_validateValidate existing flakeSchemas must be registered viaIFlakesinterface.Args:id (str): ID of flake to validate
expand (bool, optional): Extend flake using data from the parent flakes
schema(str): validation schema for the flake's dataflakes_data_validateValidate arbitrary data against the named schema(registered via IFlakes).Args:data (dict): data that needs to be validated
schema(str): validation schema for the dataflakes_data_exampleGenerate an example of the flake's data using named factory(registered via IFlakes).Factories must be registered viaIFlakesinterface.Args:factory(str): example factory
data (dict, optional): payload for the example factoryflakes_flake_materializeSend flake's data to API action.Args:id (str): ID of flake to materialize
expand (bool, optional): Extend flake using data from the parent flakes
remove (bool, optional): Remove flake after materialization
action (str): API action to use for materializationflakes_flake_combineCombine data from multiple flakesidargument specifies all the flakes that must be combined. All of the flakes
must exist, otherwiseNotFounderror raised. IDs at the start of the list have
higher priority(override matching keys). IDs at the end of the list have lower
priority(can be shadowed by former flakes).expandmust be adict[str, bool]. Keys are IDs of the flakes, values are
expand flags for the corresponding flake.Args:id (list): IDs of flakes.
expand (dict, optional): Extend flake using data from the parent flakesflakes_flake_mergeCombine multiple flakes and save the result.Args:id (list): IDs of flakes.
expand (dict, optional): Extend flake using data from the parent flakes
remove (bool, optional): Remove flakes after the operation.
destination (str, optional): Save data into the specified flake instead of a new oneflakes_data_patchPartially overrides data leaving other fields intact.Args:id (str): ID of flake
data (dict): patch for dataflakes_extras_patchPartially overrides extras leaving other fields intact.Args:id (str): ID of flake
extras (dict): patch for extrasDeveloper installationTo install ckanext-flakes for development, activate your CKAN virtualenv and
do:git clone https://github.com/DataShades/ckanext-flakes.git
cd ckanext-flakes
python setup.py developTestsTo run the tests, do:pytestLicenseAGPL