{ "info": { "author": "Mat\u011bj Fanta", "author_email": "fantamat93@gmail.com", "bugtrack_url": null, "classifiers": [ "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python :: 3", "Topic :: Scientific/Engineering", "Topic :: Software Development :: Libraries :: Python Modules" ], "description": "# ruleex\n\nThe *ruleex* is a python package which implements rule extraction algorithms.\nNow, there are available three algorithms (HypInv, ANN-DT, and DeepRED).\nAll of those algorithms were slightly modified. Especially, extracted rules are\nstored in the tree structure with decision nodes which can have any form, e.g.,\naxis parallel or linear split. Implementation of the general decision tree, i.e.,\nthe tree with any kind of decision node, is also included along with some handy\noperations.\n\nFollowing description provides only short description focused on the code (classes, \nfunctions, and their arguments). For more details of the theory behind the implementation\nthere will be available my master thesis.\n\n## tree\n\nThis subpackage contains implementation of the general decision tree, i.e.,\ndecision tree with any kind of decision nodes. It is also possible to operate with\nso-called decision graph which can have more than one incoming edges to each node.\n\n### class Rule\n\nRule is an abstract class describing the decision node inside the tree.\nObject of this class stores at least these arguments:\n\n* *true_branch and false_branch*: pointers to the next node or None.\n* *class_set*: set of classes that the node represents (in the tree \n this sets should follow the order in inclusion manner) \n* *class_hits*: the list with the number of samples from each class.\n* *num_true and num_false*: the number of samples that were redirected to \nthe true and false branch, respectively.\n\nThe methods that are needed to be overridden are:\n\n* **eval_rule**: evaluates the rule, i.e., returns True if the rule's condition is fulfilled.\n* **eval_all**: evaluates a list of samples (uses numpy to optimize the decision process).\n* **to_string**: prints the node to the string.\n* **copy**: returns a new instance as a copy of current object.\n\n\nIt is also conveniente to extend **\\_\\_init\\_\\_** method to store some other crucial value for a specific node.\n\nImplemented subclasses are AxisRule and LinearRule representing \naxis parallel and linear split node. There is also implemented Leaf - \nthe leaf node of the tree.\n\nIt is possible to implement other subclasses of the class Rule however in the tree structure\nleafs should be always of the class Leaf because the algorithms implemented in RuleTree class\nare dependent on that property.\n\n### class RuleTree\n\nThe main class of this subpackage. It represents general decision tree.\n\nThis class is not encapsulated. It is rather free to operate on. Its implementation\nserves two purpouses. The first is a storage of the general information about the tee such as\n\n- root node,\n- number of classes,\n- number of input values.\n\nAnd the second purpose is to implement some simple operation on the tree structure.\n\n1. static methods\n * rt.half_in_ruletree(number_of_classes): creates tree with the same size as the\n number of classes. This tree makes classification based on the first value that\n is grater than 0.5.\n * rt.load(filename): loads RuleTree object from the pickle file.\n2. evaluation\n * rt.eval_one(x): evaluates one sample.\n * rt.eval_all(x, all_nodes=None, prevs=None): evaluates all samples stored in the list x.\n When arguments all_nodes and prevs are passed then the method do not need to\n do recursive calls and its faster (it also holds for other methods with these\n arguments).\n3. copy, save, and print\n * rt.copy(): makes deep copy of the tree.\n * rt.save(filename): saves ruletree into the pickle file.\n * rt.to_string(): prints ruletree. Be aware of the computation complexity of this\n function when the tree is big.\n * rt.print_expanded_rules(): prints all rules, i.e., each path from \n the root to a leaf node as If-THEN rule with conjuction of the nodes conditions.\n * rt.view_graph(): returns graphviz Digraph. It also can show it and store it as pdf.\n4. getters\n * rt.get_all_nodes(): returns all nodes that decision graph contains starting\n with the rt.root.\n * rt.get_predecessor_dict(): returns a map between a node and a list of their predecessors.\n * rt.get_all_expanded_rules(): returns list of all paths in the graph that starts \n in the rt.root and ends in a leaf node.\n * rt.get_thresholds(): for the trees with Axis rules returns all thresholds\n for each attribute\n * rt.get_rule_index_dict(x): returns a map between a node and indexes\n of samples x that visited the node during the evaluation.\n * rt.get_stats(): Return some chosen properties of the ruletree:\n number of rules, number of nodes, number of used indexes \n by the tree (supports only AxisRule and LinearRule), \n a sum of the lenght of all rules, a sum of indexes used \n in each rule.\n5. graph operations\n * rt.replace_leaf_with_set(leaf_class_set, replacing_rule, all_nodes=None, prevs=None): replaces all\n leafs with the class set equal to leaf_class_set by replacing rule.\n * rt.replace_rule(matching_rule, replacement, prev_entries=None): replaces matching_rule by the replacement.\n If prev_entries are pressent then it do not recursively looking for matching_rule\n in the graph. (Hint: ```prev_entries = prevs\\[matching_rule\\]```)\n * rt.delete_node(node, branch, all_nodes, prevs): deletes the node in graph structure\n and replace it by its branch specified with the argument.\n * rt.fill_class_sets(): fills class sets accordingly to the leaf class sets.\n Node with two Leafs in branches with class sets {1} and {0,3} lead will have\n class set {0,1,3}.\n * rt.fill_hits(x, labels, remove_redundat=False): fills class_hits in all nodes\n in the graph by evaluating samples x with their true labels. If remove_redundat\n is True then the nodes that are not filled are removed.\n6. checks\n * rt.check_none_inside_tree(): returns True if only Leaf nodes has None in some branch.\n * rt.check_one_evaluation(): checks if the graph evalueates each sample to only one class.\n * rt.graph_max_indegree(all_nodes=None, return_all=False): returns maximal indegree\n occurring in the graph. And returns a map between a node and its indegree if return_all is True.\n7. condition pruning\n * rt.fill_hits with remove_redundat=True option.\n * rt.delete_interval_redundancy(): Deletes the nodes which result \n is forced by conditions of the previous nodes in the path from the rt.root to it.\n * rt.delete_class_redundacy(): Deletes the parts of the \n RuleTree structure which lead to the same result (classification).\n * rt.remove_unvisited(visited_rules): removes other nodes that specified in argument.\n * rt.remove_unvisited_edges(all_nodes, prevs): removes the nodes that has num_true or num_false equal to zero.\n * rt.prune_using_hits(min_split_fraction=0.0, min_rule_samples=1, all_nodes=None):\n Removes all nodes that have sum of class_hits less than min_rule_samples and\n ```max(num_true/num_false, num_false/num_true) < min_split_fraction```.\n\n### Building RuleTree\n\nStandard method to built DT is based on researching all possible splits and taking\nthat minimizes weighted sum of impurities of its division.\nThe main concept of this algorithm is implemented in *build* module by function\n**build_from_rules**.\n\nRequired arguments are\n* rule_list: a list of rules, e.g., object with a class AxisRule\n* data: a list of inputs of training samples\n* labels: a list of labels of training samples\n\nWarning: Do not use this function for more than 100 possible nodes. It is\ngeneral and not optimized. If you want to train standard DT use sklearn and then\nconvert the result by **sklearndt_to_ruletree** function in *ulits* module. \n(Even ANN-DT algorithm can be set to build DT in the standard way and it is optimized)\n\n### utils.py\n\nMain reason for this module is a conversion between other models of DT and the RuleTree class.\n\n**sklearndt_to_ruletree** converts sklearn model into the RuleTree object. It is also possible\nto add some additional pruning setting like min_split_fraction (see rt.prune_using_hits).\n\n**pybliquedt_to_ruletree** and **train_OC1** assumes a package [*pyblique*](https://github.com/KDercksen/pyblique).\nThey produce a RuleTree with LinearRule splits.\n\n\n### rutils.py\n\nThis module required *rpy2* and R-project installed. It provides functions to train C5.0 DT\nand convert it to the RuleTree object.\n\n## ANN-DT method (anndt subpackage)\n\nThis method is very similar to the standard DT building, but it at each node looks\nif there is enough samples and if not new samples are generated by sampling input space\nand taking the output of the NN. There are also other impurity functions because the\nNN provides continuous output for samples and not just label.\n\nThe main function is **anndt(model_fun, x, params, MeasureClass=None, stat_test=None, sampler=None, init_restrictions=None):**,\nwhere arguments are:\n* **model_fun** is a function that returns output of the NN for a list of samples when is called.\n* **x** a list of training samples.\n* **MeasureClass** class of the impurity measure (defined in module *measures*).\n* **stat_test** a function of used statistical test (some functions are defined in module *stat_test*).\n* **sampler** a subclass of Sampler defined in module *sampling*, where some examples are implemented.\nPay most of the attention to setting or implementing this class for your data.\n* **init_restriction** a list of minimal and maximal value of the input.\n* **params** a dictionary of additional parameters the main parameters are:\n * **min_samples**: minimal number of samples for creating a new node.\n This values is used to generate that many new samples to fulfill the condition.\n * **min_train**: minimal number of train samples to create a new node. If\n the number of training samples is lower building current subtree stops.\n * **max_depth**: maximal depth of a generated DT.\n * **min_split_fraction**: one of the stopping criteria see rt.prune_using_hits for more details.\n * **varbose**: a level of varbose\n * 0: nothing is printed:\n * 1: some information is printed.\n\n\n### module measures.py\n\nIn this module the abstract class *MeasureOptimized* is defined. It represent abstraction\nthat uses ANN-DT for searching for the optimal split. The main method of the class\nis *find_split*.\n\nThere are also implemented subclasses of *MeasureOptimized*:\n* **GiniMeasure**: representing gini index impurity based training algorithm.\n* **EntropyMeasure**: representing entropy impurity based training algorithm.\n* **FidelityGain**: representing fidelity gain based training algorithm. Fidelity\ngain is defined as a percentage of wrongly classified samples by the most occurring\nclass in each branch.\n* **VarianceMeasure**: this impurity can be used **only for a binary classification**.\nIt computes a variance as the impurity measure.\n\n### module sampling.py\n\nThis module defines an abstract class *Sampler* and its two subclasses *NormalSampler* and *BerNormalSampler*.\nThere are two abstract methods that have to be overwritten:\n* get_default_params(self, x): a method that sets up parameters required for sampler.\n* generate_x(self, train_x, number, restrictions): a method that generates samples.\nFor its generation there are available training samples of current node and restriction\ndefined by input space and previous nodes.\n\n**NormalSampler** defines sampling class for randomly distributed data. It is\nconvenient to use some statistical test to determine wheater your data are normally\ndistributed.\n\nIn **BerNormalSampler** the sampling is conducted by multiplication of Bernoulli and normal distribution.\nThis results in the values that are either zero or normally distributed.\nSo, there are three parameters: *p* for the Bernoulli distribution (a probability of\nthe occurrence of the zero value); *sigma* and *mu* for normal distribution.\n\n\n## DeepRED method (deepred subpackage)\n\nA method that uses activation of all layers of the NN. The algorithm takes \nall activation for training samples. It starts from the last layer. it build DT\nthat classifies samples base on the last activations. Then for each node\nthe DT is built that classifies if the node's condition hold or not. After that\nthe node is substituted by this DT. This process is repeated until the first layer\nis reached. Substitution of the nodes by the tree leads to the decision graph.\n\nThe main function is **deepred(layers_activations, params)**.\n**layers_activations** is a tensor with dimensions (layers x samples x activations).\nAn argument **params** specifies all setting of the method the main one are:\n\n* **initial_dt_train_params**: a dictionary of the parameters that are used for\ntraining the initial dt by sklearn package.\n* **dt_train_params**: a dictionary of the parameters that are used for\ntraining other than initial dt by sklearn package.\n* **build_first**: if True then the DT is builded on the last activations else\na tree with splits 0.5 is used (see half_in_ruletree method on RuleTree class).\n* **varbose**: a level of varbose\n * 0: notning is shown or printed.\n * 1: the main processing informatin is printed.\n * 2: additional information about subtrees is printed.\n * 3: graph of each step is generated as pdf along with the result. \n\nFunction **deepred** returns additional information about the process along with\nthe extracted RuleTree.\n\nThe tensorflow model of the multilayer perceptron that returns all \nactivations is included in model.py package. Its name is DeepRedFCNet.\n\n## HypInv method (hypinv subpackage)\n\nThe HypInv deals with decision boundaries defined by the NN (generally classifier).\nDecision boundaries are parts of the input space where the classification changes.\nThe method works as follows. Firstly, the wrongly classified point is found. Then,\nthe closes point on the decision boundary to that point is found. A new linear\nsplit is make as a perpendicular hyperplane to the line between those points\nintersecting with the point on the boundary. The rules are created in the form\nof the decision tree with linear splits. Building is done by selection the best\nsplits at each step in the impurity manner taking only generated linear splits into account.\n\nThere are three available methods for finding the closest point on the decision\nboundary. These methods are:\n1. generating evenly distributed points on the decision boundary by evolutionary algorithm,\n2. inversion of the NN followed by sliding along the boundary,\n3. modified cost function.\n\nTheir usage depends on the dimensionality of the input. The boundaries between \nthese three methods is roughly 60, 300. So, in dimension under 60 the generation\npoint on the boundary can be efficient, but not for dimension 500 where only\nrecommended method is to use modified cost function.\n\nThis method was used only on data with high dimensional spaces so\nthe evolutionary search for points on the decision boundary is not supported.\n\nThe main function is **hypinv(model, x, params=dict(), input_range=None)**.\n* **model** an object with subclass NetForHypinv (in module hypinv.model).\nIt also determine if the modified loss is used.\n* **x** a list of training samples (just inputs).\n* **input_range** a range of the input space, e.g., [[0, 255] for i in range(dimensions)] for one bite inputs.\nIf is not defined the algorithm set it by taking maximal and minimal value of the attributes of training samples.\n* **params** specifies all setting of the method the main one are:\n * **max_rules** maximal number of linear splits that will be created.\n * **max_depth** maximal depth of the generated tree.\n * **thresh_fidelity** if fidelity exceeds this value then the algorithm stops and return current RuleTree.\n * **use_training_points** if True then search for badly classified points is done in the training set else \n the algorithm uniformly samples input space to find point where the classification\n of the NN and current RuleTree is different. \n * **max_sliding_steps** the maximal number of steps in the sliding procedure.\n * **gtrain_params** a dictionary with gtrain parameters that are used for\n optimization modified cost function.\n\nFunction **hypinv** returns additional information about the process along with\nthe extracted RuleTree.\n\n\n## Future work\n\n\nI have noticed that there are no recent attempts to extend decision trees (DTs).\nEventhough, the HypInv rule extraction method is using linear splits \nand achieves very good results in cases where other methods or DTs fails. \nSo, DT with linear (oblique) splits can be very promising direction\nof research. However, general linear splits are hard to explain. Therefore, a new\nmethod should have following properties:\n\n2. Good separation of classes - it is the same properties as for DTs\n2. Low number of attributes in combination\n\nThe idea is to define parametric function and by its optimization find desired\nattributes of the linear combination. However, plain iterative optimization will\ndo not lead to setting some attributes zeros which is required by second property.\n\n\n\n\n", "description_content_type": "text/markdown", "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/fantamat/ruleex", "keywords": "", "license": "MIT", "maintainer": "", "maintainer_email": "", "name": "ruleex", "package_url": "https://pypi.org/project/ruleex/", "platform": "", "project_url": "https://pypi.org/project/ruleex/", "project_urls": { "Homepage": "https://github.com/fantamat/ruleex" }, "release_url": "https://pypi.org/project/ruleex/0.4.1/", "requires_dist": null, "requires_python": "", "summary": "Rule extraction methods from neural networks", "version": "0.4.1", "yanked": false, "yanked_reason": null }, "last_serial": 6049459, "releases": { "0.3.1": [ { "comment_text": "", "digests": { "md5": "d28560d6ba679a52dc67fccf68fd95fa", "sha256": "6471c7d4b7a0a32f6e85dec44b7585cc8f55c069a853af3d8cc782a469ac2a3a" }, "downloads": -1, "filename": "ruleex-0.3.1-py3-none-any.whl", "has_sig": false, "md5_digest": "d28560d6ba679a52dc67fccf68fd95fa", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 1405, "upload_time": "2019-05-04T22:48:06", "upload_time_iso_8601": "2019-05-04T22:48:06.513601Z", "url": "https://files.pythonhosted.org/packages/1e/9c/22cd897c09c44573bb2bb1d9b72fe244d644b7fb7774ad9a92e63bbde109/ruleex-0.3.1-py3-none-any.whl", "yanked": false, "yanked_reason": null }, { "comment_text": "", "digests": { "md5": "a8f8b86c15b92ef8185d044409bd4631", "sha256": "c0b9e46459980511b2fc043492133e74ede194faa9919d7f3bb5dfd2a83c7e3d" }, "downloads": -1, "filename": "ruleex-0.3.1.tar.gz", "has_sig": false, "md5_digest": "a8f8b86c15b92ef8185d044409bd4631", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 1102, "upload_time": "2019-05-04T22:48:08", "upload_time_iso_8601": "2019-05-04T22:48:08.679820Z", "url": "https://files.pythonhosted.org/packages/9a/90/49897067223d892b7569280cfa6c7dcbbb69ef3d1f93fefdeb3f36a22062/ruleex-0.3.1.tar.gz", "yanked": false, "yanked_reason": null } ], "0.3.2": [ { "comment_text": "", "digests": { "md5": "f22508779e0d728b3def890020855d81", "sha256": "a2482928bc1979d15236dc51d8751c4a595f8dff0e5f961099ff8d3b0eabdeae" }, "downloads": -1, "filename": "ruleex-0.3.2-py3-none-any.whl", "has_sig": false, "md5_digest": "f22508779e0d728b3def890020855d81", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 38748, "upload_time": "2019-05-04T22:53:24", "upload_time_iso_8601": "2019-05-04T22:53:24.843821Z", "url": "https://files.pythonhosted.org/packages/bd/e8/0163a0c4ac8a76bdeab26e7dd248635c2105f666463410acfd2f65175ac6/ruleex-0.3.2-py3-none-any.whl", "yanked": false, "yanked_reason": null }, { "comment_text": "", "digests": { "md5": "50ad5e4978679b8bd910dcea23878503", "sha256": "af6fddc1544d0d03fdfbaa7bc90375b25686e24c4b0cf52a3bcd38eded8a8018" }, "downloads": -1, "filename": "ruleex-0.3.2.tar.gz", "has_sig": false, "md5_digest": "50ad5e4978679b8bd910dcea23878503", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 32568, "upload_time": "2019-05-04T22:53:26", "upload_time_iso_8601": "2019-05-04T22:53:26.392474Z", "url": "https://files.pythonhosted.org/packages/81/81/bafd805ad7af86dbfabb5461bfb34e8e160467a8e0988ceb90cdebf0cf83/ruleex-0.3.2.tar.gz", "yanked": false, "yanked_reason": null } ], "0.3.3": [ { "comment_text": "", "digests": { "md5": "8e66c1643162598a7a319b4a48b27384", "sha256": "592200054eb8e425149a0409a179a290458259e717e258bd4cfa15901eea54e4" }, "downloads": -1, "filename": "ruleex-0.3.3-py3-none-any.whl", "has_sig": false, "md5_digest": "8e66c1643162598a7a319b4a48b27384", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 38739, "upload_time": "2019-05-04T22:55:41", "upload_time_iso_8601": "2019-05-04T22:55:41.971246Z", "url": "https://files.pythonhosted.org/packages/5c/39/65560045642ef56db62381fc47a1bc11c1d7e528067f5bcf290c0d3ff85e/ruleex-0.3.3-py3-none-any.whl", "yanked": false, "yanked_reason": null }, { "comment_text": "", "digests": { "md5": "5afb59317f564898ffb2dcb3204f3bf2", "sha256": "69df7d484967f04ae8f95f908146b80b0744786ae990d105999f2c11fa8e29c4" }, "downloads": -1, "filename": "ruleex-0.3.3.tar.gz", "has_sig": false, "md5_digest": "5afb59317f564898ffb2dcb3204f3bf2", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 32564, "upload_time": "2019-05-04T22:55:43", "upload_time_iso_8601": "2019-05-04T22:55:43.638662Z", "url": "https://files.pythonhosted.org/packages/64/81/7d83a98f4192549471f5a4fde05d9c9358138b5d812e82bfc71dea907428/ruleex-0.3.3.tar.gz", "yanked": false, "yanked_reason": null } ], "0.3.4": [ { "comment_text": "", "digests": { "md5": "cdac6cf8a5102c078cf9f00cc29d0c2a", "sha256": "c6e71b70280ac0a30343e24f3907d57231624e5f58108a35b1d12f831334f88f" }, "downloads": -1, "filename": "ruleex-0.3.4-py3-none-any.whl", "has_sig": false, "md5_digest": "cdac6cf8a5102c078cf9f00cc29d0c2a", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 38758, "upload_time": "2019-05-04T23:39:02", "upload_time_iso_8601": "2019-05-04T23:39:02.805212Z", "url": "https://files.pythonhosted.org/packages/a6/bd/07a3a34fa4aeb56714532b0a3f613c5ed85e36640afd5c63ad807691bf8c/ruleex-0.3.4-py3-none-any.whl", "yanked": false, "yanked_reason": null }, { "comment_text": "", "digests": { "md5": "6eeeb31dfdb3d6d6676492e820f0a8ef", "sha256": "13c1a25f055f023e2ec9046ef2d1f45b2fbf71b8b7048eb35b118466a406d8e1" }, "downloads": -1, "filename": "ruleex-0.3.4.tar.gz", "has_sig": false, "md5_digest": "6eeeb31dfdb3d6d6676492e820f0a8ef", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 32591, "upload_time": "2019-05-04T23:39:04", "upload_time_iso_8601": "2019-05-04T23:39:04.447726Z", "url": "https://files.pythonhosted.org/packages/4e/c6/0795cff4639593dc3b7dd78c17e49a88c2453481acf1da09008e9a07c98a/ruleex-0.3.4.tar.gz", "yanked": false, "yanked_reason": null } ], "0.4.0": [ { "comment_text": "", "digests": { "md5": "fc43818dc5f4a244e476b453454db290", "sha256": "fedc70c02810636df25a45c61a4c1266e721a3dd4abdee2126dbb613ceb4abac" }, "downloads": -1, "filename": "ruleex-0.4.0-py3-none-any.whl", "has_sig": false, "md5_digest": "fc43818dc5f4a244e476b453454db290", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 47428, "upload_time": "2019-08-03T16:51:50", "upload_time_iso_8601": "2019-08-03T16:51:50.057059Z", "url": "https://files.pythonhosted.org/packages/fa/76/fa5f3a203bd8a2e773318d9b39dbcc1605a246eedb22667f59f7f63ee207/ruleex-0.4.0-py3-none-any.whl", "yanked": false, "yanked_reason": null }, { "comment_text": "", "digests": { "md5": "6d0730f4e4b9e1309e30315866cd0d68", "sha256": "fbda630ea2cc08e611075c914f0bfa415e94095e8cd97b24c1dfa8c0fac57513" }, "downloads": -1, "filename": "ruleex-0.4.0.tar.gz", "has_sig": false, "md5_digest": "6d0730f4e4b9e1309e30315866cd0d68", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 47285, "upload_time": "2019-08-03T16:51:51", "upload_time_iso_8601": "2019-08-03T16:51:51.432725Z", "url": "https://files.pythonhosted.org/packages/9f/7c/18b4e3233e28cc72f775fe12d32eba55c9d044c5be6e677ac6588200a290/ruleex-0.4.0.tar.gz", "yanked": false, "yanked_reason": null } ], "0.4.1": [ { "comment_text": "", "digests": { "md5": "571ba98bed636b57ec3a72d5b266c3d0", "sha256": "5e26af2678fb70c16d4953621d97785cb50107950ce524a4ecf3d21a89430173" }, "downloads": -1, "filename": "ruleex-0.4.1-py3-none-any.whl", "has_sig": false, "md5_digest": "571ba98bed636b57ec3a72d5b266c3d0", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 47729, "upload_time": "2019-10-29T21:47:46", "upload_time_iso_8601": "2019-10-29T21:47:46.846638Z", "url": "https://files.pythonhosted.org/packages/01/94/940af06e01b00b574944e4752596571c6d2d6635dc0fd5a381d61126286e/ruleex-0.4.1-py3-none-any.whl", "yanked": false, "yanked_reason": null }, { "comment_text": "", "digests": { "md5": "59fa9dccde834a93924082b23ac51784", "sha256": "b548ddefa96289efaad11c092c50ba698feb2da408e2394d4a6367e85605c2b8" }, "downloads": -1, "filename": "ruleex-0.4.1.tar.gz", "has_sig": false, "md5_digest": "59fa9dccde834a93924082b23ac51784", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 47600, "upload_time": "2019-10-29T21:47:48", "upload_time_iso_8601": "2019-10-29T21:47:48.595300Z", "url": "https://files.pythonhosted.org/packages/ec/5c/87adc98851991bb0ffb556af011be39966b908d63104648d42291984ebae/ruleex-0.4.1.tar.gz", "yanked": false, "yanked_reason": null } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "571ba98bed636b57ec3a72d5b266c3d0", "sha256": "5e26af2678fb70c16d4953621d97785cb50107950ce524a4ecf3d21a89430173" }, "downloads": -1, "filename": "ruleex-0.4.1-py3-none-any.whl", "has_sig": false, "md5_digest": "571ba98bed636b57ec3a72d5b266c3d0", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 47729, "upload_time": "2019-10-29T21:47:46", "upload_time_iso_8601": "2019-10-29T21:47:46.846638Z", "url": "https://files.pythonhosted.org/packages/01/94/940af06e01b00b574944e4752596571c6d2d6635dc0fd5a381d61126286e/ruleex-0.4.1-py3-none-any.whl", "yanked": false, "yanked_reason": null }, { "comment_text": "", "digests": { "md5": "59fa9dccde834a93924082b23ac51784", "sha256": "b548ddefa96289efaad11c092c50ba698feb2da408e2394d4a6367e85605c2b8" }, "downloads": -1, "filename": "ruleex-0.4.1.tar.gz", "has_sig": false, "md5_digest": "59fa9dccde834a93924082b23ac51784", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 47600, "upload_time": "2019-10-29T21:47:48", "upload_time_iso_8601": "2019-10-29T21:47:48.595300Z", "url": "https://files.pythonhosted.org/packages/ec/5c/87adc98851991bb0ffb556af011be39966b908d63104648d42291984ebae/ruleex-0.4.1.tar.gz", "yanked": false, "yanked_reason": null } ], "vulnerabilities": [] }