pasturepy is a Python tool for generating JSON feed definitions for use with Graze. Use it to programmatically create and customize feeds for Graze.
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

chore: update w ruff linting

+304 -200
+12 -19
pasturepy/constants/__init__.py
··· 1 - from .fields import ( 2 - TEXT_FIELDS, 3 - OPTION_FIELDS, 4 - NUMBER_FIELDS, 5 - ENTITY_TYPES, 6 - EMBED_TYPES 7 - ) 1 + from .fields import EMBED_TYPES, ENTITY_TYPES, NUMBER_FIELDS, OPTION_FIELDS, TEXT_FIELDS 8 2 from .graze_json import ( 9 - REGEX_METHODS, 10 - WORD_METHODS, 11 - ENTITY_METHODS, 12 3 COMPARISONS, 13 4 EMBED_COMPARISONS, 14 - MEMBER_TYPES, 5 + ENTITY_METHODS, 6 + REGEX_METHODS, 15 7 SOCIAL_LISTS, 8 + STATUS_TYPES, 9 + WORD_METHODS, 16 10 ) 17 - 18 11 from .values import ( 12 + CATEGORIES, 19 13 CONTENT_MODS, 14 + EMOTIONS, 20 15 IMAGE_MODS, 21 - TOPICS, 16 + LABELS, 22 17 LANGUAGES, 23 18 SENTIMENTS, 19 + SPAM_TYPE, 20 + TOPICS, 24 21 TOXICITY, 25 - EMOTIONS, 26 - CATEGORIES, 27 - SPAM_TYPE, 28 - LABELS, 29 22 ) 30 23 31 24 __all__ = [ ··· 39 32 "ENTITY_METHODS", 40 33 "COMPARISONS", 41 34 "EMBED_COMPARISONS", 42 - "MEMBER_TYPES", 35 + "STATUS_TYPES", 43 36 "SOCIAL_LISTS", 44 37 "CONTENT_MODS", 45 38 "IMAGE_MODS", ··· 51 44 "CATEGORIES", 52 45 "SPAM_TYPE", 53 46 "LABELS", 54 - ] 47 + ]
+123 -39
pasturepy/constants/values.py
··· 1 1 # Values for ML filters (moderation, sentiment, etc.) 2 - CONTENT_MODS = {"OK", "sexual", "sexual/minors", "violence/graphic", 3 - "violence", "self-harm", "hate", "hate/threatening", 4 - "harassment"} 2 + CONTENT_MODS = { 3 + "OK", 4 + "sexual", 5 + "sexual/minors", 6 + "violence/graphic", 7 + "violence", 8 + "self-harm", 9 + "hate", 10 + "hate/threatening", 11 + "harassment", 12 + } 5 13 IMAGE_MODS = {"SFW", "NSFW"} 6 - TOPICS = {"Arts & Culture", "Business & Entrepreneurs", 7 - "Celebrity & Pop Culture", "Diaries & Daily Life", 8 - "Family", "Fashion & Style", "Film, TV & Video", 9 - "Fitness & Health", "Food & Dining", "Gaming", 10 - "Learning & Educational", "Music", 11 - "News & Social Concern", "Other Hobbies", 12 - "Relationships", "Science & Technology", "Sports", 13 - "Travel & Adventure", "Youth & Student Life"} 14 - LANGUAGES = {"English", "Japanese", "Dutch", "Arabic", "Polish", 15 - "German", "Italian", "Portuguese", "Turkish", 16 - "Spanish", "Hindi", "Greek", "Urdu", "Bulgarian", 17 - "French", "Chinese", "Russian", "Thai", "Swahili", 18 - "Vietnamese"} 14 + TOPICS = { 15 + "Arts & Culture", 16 + "Business & Entrepreneurs", 17 + "Celebrity & Pop Culture", 18 + "Diaries & Daily Life", 19 + "Family", 20 + "Fashion & Style", 21 + "Film, TV & Video", 22 + "Fitness & Health", 23 + "Food & Dining", 24 + "Gaming", 25 + "Learning & Educational", 26 + "Music", 27 + "News & Social Concern", 28 + "Other Hobbies", 29 + "Relationships", 30 + "Science & Technology", 31 + "Sports", 32 + "Travel & Adventure", 33 + "Youth & Student Life", 34 + } 35 + LANGUAGES = { 36 + "English", 37 + "Japanese", 38 + "Dutch", 39 + "Arabic", 40 + "Polish", 41 + "German", 42 + "Italian", 43 + "Portuguese", 44 + "Turkish", 45 + "Spanish", 46 + "Hindi", 47 + "Greek", 48 + "Urdu", 49 + "Bulgarian", 50 + "French", 51 + "Chinese", 52 + "Russian", 53 + "Thai", 54 + "Swahili", 55 + "Vietnamese", 56 + } 19 57 SENTIMENTS = {"Positive", "Negative", "Neutral"} 20 - TOXICITY = {"Toxic", "Severe Toxicity", "Obscene", "Threat", 21 - "Insult", "Identity Hate"} 22 - EMOTIONS = {"Admiration", "Amusement", "Anger", "Annoyance", 23 - "Approval", "Caring", "Confusion", "Curiosity", 24 - "Desire", "Disappointment", "Disapproval", "Disgust", 25 - "Embarrassment", "Excitement", "Fear", "Gratitude", 26 - "Grief", "Joy", "Love", "Nervousness", "Optimism", 27 - "Pride", "Realization", "Relief", "Remorse", 28 - "Sadness", "Surprise", "Neutral"} 29 - CATEGORIES = {"Academic & Intellectual", "Adult & Sexual Content", 30 - "AI & Machine Learning", "Animals", "Arts & Creative", 31 - "Aviation & Maritime", "Data & Computing", 32 - "Entertainment & Culture", "Film & TV", 33 - "Food & Beverages", "Food & Lifestyle", 34 - "Game Development", "Gaming", "Healthcare & Medicine", 35 - "Medical Education", "Medical Specialties", "Music", 36 - "Nature & Outdoors", "News & Media", "Politics", 37 - "Programming", "Society & Culture", 38 - "Software Development", "Sports", "Visual Arts"} 58 + TOXICITY = {"Toxic", "Severe Toxicity", "Obscene", "Threat", "Insult", "Identity Hate"} 59 + EMOTIONS = { 60 + "Admiration", 61 + "Amusement", 62 + "Anger", 63 + "Annoyance", 64 + "Approval", 65 + "Caring", 66 + "Confusion", 67 + "Curiosity", 68 + "Desire", 69 + "Disappointment", 70 + "Disapproval", 71 + "Disgust", 72 + "Embarrassment", 73 + "Excitement", 74 + "Fear", 75 + "Gratitude", 76 + "Grief", 77 + "Joy", 78 + "Love", 79 + "Nervousness", 80 + "Optimism", 81 + "Pride", 82 + "Realization", 83 + "Relief", 84 + "Remorse", 85 + "Sadness", 86 + "Surprise", 87 + "Neutral", 88 + } 89 + CATEGORIES = { 90 + "Academic & Intellectual", 91 + "Adult & Sexual Content", 92 + "AI & Machine Learning", 93 + "Animals", 94 + "Arts & Creative", 95 + "Aviation & Maritime", 96 + "Data & Computing", 97 + "Entertainment & Culture", 98 + "Film & TV", 99 + "Food & Beverages", 100 + "Food & Lifestyle", 101 + "Game Development", 102 + "Gaming", 103 + "Healthcare & Medicine", 104 + "Medical Education", 105 + "Medical Specialties", 106 + "Music", 107 + "Nature & Outdoors", 108 + "News & Media", 109 + "Politics", 110 + "Programming", 111 + "Society & Culture", 112 + "Software Development", 113 + "Sports", 114 + "Visual Arts", 115 + } 39 116 SPAM_TYPE = {"Marketing Spam", "Organic Content"} 40 - LABELS = {"porn", "sexual", "nudity", "graphic-media", 41 - "spam", "rude", "!no-unauthenticated", 42 - "bridged-from-bridgy-fed-web", 43 - "bridged-from-bridgy-fed-activitypub"} 117 + LABELS = { 118 + "porn", 119 + "sexual", 120 + "nudity", 121 + "graphic-media", 122 + "spam", 123 + "rude", 124 + "!no-unauthenticated", 125 + "bridged-from-bridgy-fed-web", 126 + "bridged-from-bridgy-fed-activitypub", 127 + }
+1 -1
pasturepy/core/__init__.py
··· 2 2 from .filter import FilterGroup 3 3 from .sort import SortSettings 4 4 5 - __all__ = ["FeedConfig", "FilterGroup", "SortSettings", "OrderType"] 5 + __all__ = ["FeedConfig", "FilterGroup", "SortSettings", "OrderType"]
+16 -15
pasturepy/core/config.py
··· 1 - from typing import Dict, Any, Optional, Literal 1 + from typing import Any, Dict, Literal, Optional 2 + 2 3 from .filter import FilterGroup 3 4 from .sort import SortSettings 4 5 5 6 OrderType = Literal["new", "trending", "blend"] 7 + 6 8 7 9 class FeedConfig: 8 10 """Configure feed filtering and sorting settings.""" 9 - 11 + 10 12 def __init__(self, order: OrderType = "blend"): 11 13 self.order = order 12 14 self.filters = FilterGroup("and") 13 15 self._sort_settings = None 14 - 15 - def set_sort_order(self, order: OrderType) -> 'FeedConfig': 16 + 17 + def set_sort_order(self, order: OrderType) -> "FeedConfig": 16 18 """Set the feed sorting order.""" 17 19 self.order = order 18 20 if order == "new": ··· 20 22 elif order in ["trending", "blend"] and self._sort_settings is None: 21 23 self._sort_settings = SortSettings() 22 24 return self 23 - 24 - def set_sort_settings(self, **kwargs) -> 'FeedConfig': 25 + 26 + def set_sort_settings(self, **kwargs) -> "FeedConfig": 25 27 """Set custom sort settings for trending/blend orders.""" 26 28 if self.order == "new": 27 29 raise ValueError("No settings needed for 'new' order type") 28 - 30 + 29 31 from .sort import SortSettings 32 + 30 33 self._sort_settings = SortSettings(**kwargs) 31 34 return self 32 - 35 + 33 36 def to_dict(self) -> Dict[str, Any]: 34 37 """Convert to Graze JSON format.""" 35 - config = { 36 - "order": self.order, 37 - "manifest": {"filter": self.filters.to_dict()} 38 - } 38 + config = {"order": self.order, "manifest": {"filter": self.filters.to_dict()}} 39 39 if self._sort_settings: 40 40 config["custom_sort_settings"] = self._sort_settings.to_dict() 41 41 return config 42 - 42 + 43 43 def generate(self, output_file: Optional[str] = None) -> Dict[str, Any]: 44 44 """Generate config and optionally write to file.""" 45 45 config = self.to_dict() 46 46 if output_file: 47 47 import json 48 - with open(output_file, 'w', encoding='utf-8') as f: 48 + 49 + with open(output_file, "w", encoding="utf-8") as f: 49 50 json.dump(config, f, indent=2) 50 - return config 51 + return config
+13 -11
pasturepy/core/filter.py
··· 1 - from typing import Dict, Any, List, Union, Literal 1 + from typing import Any, Dict, List, Literal, Union 2 2 3 3 LogicType = Literal["and", "or"] 4 + 4 5 5 6 class FilterGroup: 6 7 """Represents a group of filters connected by a logical operator.""" 7 - 8 + 8 9 def __init__(self, logic_type: LogicType): 9 10 self.logic_type = logic_type 10 - self.filters: List[Union[Dict[str, Any], 'FilterGroup']] = [] 11 - 12 - def nest_filters(self, logic_type: LogicType) -> 'FilterGroup': 11 + self.filters: List[Union[Dict[str, Any], "FilterGroup"]] = [] 12 + 13 + def nest_filters(self, logic_type: LogicType) -> "FilterGroup": 13 14 """Create a nested filter group.""" 14 15 nested = FilterGroup(logic_type) 15 16 self.filters.append(nested) 16 17 return nested 17 - 18 - def add_filter(self, filter_dict: Dict[str, Any]) -> 'FilterGroup': 18 + 19 + def add_filter(self, filter_dict: Dict[str, Any]) -> "FilterGroup": 19 20 """Add a filter dictionary.""" 20 21 self.filters.append(filter_dict) 21 22 return self 22 - 23 + 23 24 def to_dict(self) -> Dict[str, Any]: 24 25 """Convert to JSON format.""" 25 - filters = [f.to_dict() if isinstance(f, FilterGroup) else f 26 - for f in self.filters] 27 - return {self.logic_type: filters} if filters else {} 26 + filters = [ 27 + f.to_dict() if isinstance(f, FilterGroup) else f for f in self.filters 28 + ] 29 + return {self.logic_type: filters} if filters else {}
+15 -14
pasturepy/core/sort.py
··· 1 - from typing import Dict, Any, Union 1 + from typing import Any, Dict, Union 2 2 3 3 Number = Union[int, float] 4 4 5 + 5 6 class SortSettings: 6 7 """Configure sort settings for trending and blend orders.""" 7 - 8 + 8 9 DEFAULTS = { 9 - 'time_window': 168, 10 - 'decay_penalty': 0.4, 11 - 'like_count_multiplier': 1, 12 - 'reply_count_multiplier': 1, 13 - 'repost_count_multiplier': 1, 14 - 'reader_like_count_multiplier': 1, 15 - 'reader_reply_count_multiplier': 0, 16 - 'reader_repost_count_multiplier': 0, 10 + "time_window": 168, 11 + "decay_penalty": 0.4, 12 + "like_count_multiplier": 1, 13 + "reply_count_multiplier": 1, 14 + "repost_count_multiplier": 1, 15 + "reader_like_count_multiplier": 1, 16 + "reader_reply_count_multiplier": 0, 17 + "reader_repost_count_multiplier": 0, 17 18 "request_less_count_multiplier": 1, 18 - "request_more_count_multiplier": 1 19 + "request_more_count_multiplier": 1, 19 20 } 20 - 21 + 21 22 def __init__(self, **kwargs): 22 23 for key, default in self.DEFAULTS.items(): 23 24 setattr(self, key, kwargs.get(key, default)) 24 - 25 + 25 26 def to_dict(self) -> Dict[str, Any]: 26 27 """Convert to JSON format.""" 27 - return {key: getattr(self, key) for key in self.DEFAULTS.keys()} 28 + return {key: getattr(self, key) for key in self.DEFAULTS.keys()}
+3 -3
pasturepy/nodes/__init__.py
··· 1 - from .text import TextNode 1 + from .embed import EmbedNode 2 2 from .entity import EntityNode 3 - from .embed import EmbedNode 4 3 from .ml import MLNode 5 4 from .social import SocialNode 5 + from .text import TextNode 6 6 7 - __all__ = ["TextNode", "EntityNode", "EmbedNode", "MLNode", "SocialNode"] 7 + __all__ = ["TextNode", "EntityNode", "EmbedNode", "MLNode", "SocialNode"]
+9 -7
pasturepy/nodes/embed.py
··· 1 1 from pasturepy.constants.fields import EMBED_TYPES 2 2 from pasturepy.constants.graze_json import EMBED_COMPARISONS 3 3 4 + 4 5 class EmbedNode: 5 - 6 6 @staticmethod 7 7 def embed(filter_group, comparison: str, embed_type: str): 8 8 """Filter by embeds (images, videos, gifs, links)""" 9 9 if embed_type not in EMBED_TYPES: 10 - raise ValueError(f"Invalid method '{embed_type}'. Must be one of {EMBED_TYPES}") 10 + raise ValueError( 11 + f"Invalid method '{embed_type}'. Must be one of {EMBED_TYPES}" 12 + ) 11 13 if comparison not in EMBED_COMPARISONS: 12 - raise ValueError(f"Invalid entity_type '{comparison}'. Must be one of {EMBED_COMPARISONS}") 13 - 14 - return filter_group.add_filter({ 15 - "embed_type": [comparison, embed_type] 16 - }) 14 + raise ValueError( 15 + f"Invalid entity_type '{comparison}'. Must be one of {EMBED_COMPARISONS}" 16 + ) 17 + 18 + return filter_group.add_filter({"embed_type": [comparison, embed_type]})
+9 -7
pasturepy/nodes/entity.py
··· 1 1 from pasturepy.constants.fields import ENTITY_TYPES 2 2 from pasturepy.constants.graze_json import ENTITY_METHODS 3 3 4 + 4 5 class EntityNode: 5 - 6 6 @staticmethod 7 7 def entity(filter_group, method: str, entity_type: str, terms: list): 8 8 """Filter by entities (hashtags, mentions, domains, etc.).""" 9 9 if method not in ENTITY_METHODS: 10 - raise ValueError(f"Invalid method '{method}'. Must be one of {ENTITY_METHODS}") 10 + raise ValueError( 11 + f"Invalid method '{method}'. Must be one of {ENTITY_METHODS}" 12 + ) 11 13 if entity_type not in ENTITY_TYPES: 12 - raise ValueError(f"Invalid entity_type '{entity_type}'. Must be one of {ENTITY_TYPES}") 13 - 14 - return filter_group.add_filter({ 15 - method: [entity_type, terms] 16 - }) 14 + raise ValueError( 15 + f"Invalid entity_type '{entity_type}'. Must be one of {ENTITY_TYPES}" 16 + ) 17 + 18 + return filter_group.add_filter({method: [entity_type, terms]})
+62 -55
pasturepy/nodes/ml.py
··· 1 1 from pasturepy.constants.graze_json import COMPARISONS 2 2 from pasturepy.constants.values import ( 3 - CONTENT_MODS, IMAGE_MODS, TOPICS, LANGUAGES, 4 - SENTIMENTS, TOXICITY, EMOTIONS, CATEGORIES, SPAM_TYPE 3 + CATEGORIES, 4 + CONTENT_MODS, 5 + EMOTIONS, 6 + IMAGE_MODS, 7 + LANGUAGES, 8 + SENTIMENTS, 9 + SPAM_TYPE, 10 + TOPICS, 11 + TOXICITY, 5 12 ) 13 + 6 14 7 15 class MLNode: 8 - 9 16 @staticmethod 10 17 def _validate_comparison(comparison: str) -> None: 11 18 if comparison not in COMPARISONS: 12 - raise ValueError(f"Invalid comparison '{comparison}'. Must be one of {COMPARISONS}") 13 - 19 + raise ValueError( 20 + f"Invalid comparison '{comparison}'. Must be one of {COMPARISONS}" 21 + ) 22 + 14 23 @staticmethod 15 24 def _validate_probability(value: float) -> None: 16 25 if not (0 <= value <= 1): 17 26 raise ValueError(f"Probability must be between 0 and 1, got {value}") 18 27 if round(value, 2) != value: 19 28 raise ValueError("Probability can only have up to 2 decimal places") 20 - 29 + 21 30 @staticmethod 22 - def content_moderation(filter_group, content_type: str, comparison: str, value: float): 31 + def content_moderation( 32 + filter_group, content_type: str, comparison: str, value: float 33 + ): 23 34 if content_type not in CONTENT_MODS: 24 35 raise ValueError(f"Invalid content_type. Must be one of {CONTENT_MODS}") 25 36 MLNode._validate_comparison(comparison) 26 37 MLNode._validate_probability(value) 27 - 28 - return filter_group.add_filter({ 29 - "content_moderation": [content_type, comparison, value] 30 - }) 31 - 38 + 39 + return filter_group.add_filter( 40 + {"content_moderation": [content_type, comparison, value]} 41 + ) 42 + 32 43 @staticmethod 33 44 def image_nsfw(filter_group, mod_type: str, comparison: str, value: float): 34 45 if mod_type not in IMAGE_MODS: 35 46 raise ValueError(f"Invalid mod_type. Must be one of {IMAGE_MODS}") 36 47 MLNode._validate_comparison(comparison) 37 48 MLNode._validate_probability(value) 38 - 39 - return filter_group.add_filter({ 40 - "image_nsfw": [mod_type, comparison, value] 41 - }) 42 - 49 + 50 + return filter_group.add_filter({"image_nsfw": [mod_type, comparison, value]}) 51 + 43 52 @staticmethod 44 53 def language(filter_group, language: str, comparison: str, value: float): 45 54 if language not in LANGUAGES: 46 55 raise ValueError(f"Invalid language. Must be one of {LANGUAGES}") 47 56 MLNode._validate_comparison(comparison) 48 57 MLNode._validate_probability(value) 49 - 50 - return filter_group.add_filter({ 51 - "language_analysis": [language, comparison, value] 52 - }) 58 + 59 + return filter_group.add_filter( 60 + {"language_analysis": [language, comparison, value]} 61 + ) 53 62 54 63 @staticmethod 55 64 def sentiment(filter_group, sentiment: str, comparison: str, value: float): ··· 57 66 raise ValueError(f"Invalid sentiment. Must be one of {SENTIMENTS}") 58 67 MLNode._validate_comparison(comparison) 59 68 MLNode._validate_probability(value) 60 - 61 - return filter_group.add_filter({ 62 - "sentiment_analysis": [sentiment, comparison, value] 63 - }) 64 - 69 + 70 + return filter_group.add_filter( 71 + {"sentiment_analysis": [sentiment, comparison, value]} 72 + ) 73 + 65 74 @staticmethod 66 75 def toxicity(filter_group, toxicity_type: str, comparison: str, value: float): 67 76 if toxicity_type not in TOXICITY: 68 77 raise ValueError(f"Invalid toxicity_type. Must be one of {TOXICITY}") 69 78 MLNode._validate_comparison(comparison) 70 79 MLNode._validate_probability(value) 71 - 72 - return filter_group.add_filter({ 73 - "toxicity_analysis": [toxicity_type, comparison, value] 74 - }) 75 - 80 + 81 + return filter_group.add_filter( 82 + {"toxicity_analysis": [toxicity_type, comparison, value]} 83 + ) 84 + 76 85 @staticmethod 77 86 def topic(filter_group, topic_type: str, comparison: str, value: float): 78 87 if topic_type not in TOPICS: 79 88 raise ValueError(f"Invalid topic_type. Must be one of {TOPICS}") 80 89 MLNode._validate_comparison(comparison) 81 90 MLNode._validate_probability(value) 82 - 83 - return filter_group.add_filter({ 84 - "topic_analysis": [topic_type, comparison, value] 85 - }) 86 - 91 + 92 + return filter_group.add_filter( 93 + {"topic_analysis": [topic_type, comparison, value]} 94 + ) 95 + 87 96 @staticmethod 88 97 def emotion(filter_group, emotion: str, comparison: str, value: float): 89 98 if emotion not in EMOTIONS: 90 99 raise ValueError(f"Invalid emotion. Must be one of {EMOTIONS}") 91 100 MLNode._validate_comparison(comparison) 92 101 MLNode._validate_probability(value) 93 - 94 - return filter_group.add_filter({ 95 - "topic_analysis": [emotion, comparison, value] 96 - }) 97 - 102 + 103 + return filter_group.add_filter({"topic_analysis": [emotion, comparison, value]}) 104 + 98 105 @staticmethod 99 106 def spam(filter_group, marketing_type: str, comparison: str, value: float): 100 107 if marketing_type not in SPAM_TYPE: 101 108 raise ValueError(f"Invalid marketing_type. Must be one of {SPAM_TYPE}") 102 109 MLNode._validate_comparison(comparison) 103 110 MLNode._validate_probability(value) 104 - 105 - return filter_group.add_filter({ 106 - "marketing_check": [marketing_type, comparison, value] 107 - }) 108 - 111 + 112 + return filter_group.add_filter( 113 + {"marketing_check": [marketing_type, comparison, value]} 114 + ) 115 + 109 116 @staticmethod 110 117 def img_category(filter_group, img_topic: str, comparison: str, value: float): 111 118 if img_topic not in CATEGORIES: 112 119 raise ValueError(f"Invalid img_topic. Must be one of {CATEGORIES}") 113 120 MLNode._validate_comparison(comparison) 114 121 MLNode._validate_probability(value) 115 - 116 - return filter_group.add_filter({ 117 - "image_arbitary": [img_topic, comparison, value] 118 - }) 119 - 122 + 123 + return filter_group.add_filter( 124 + {"image_arbitary": [img_topic, comparison, value]} 125 + ) 126 + 120 127 @staticmethod 121 128 def txt_category(filter_group, txt_topic: str, comparison: str, value: float): 122 129 if txt_topic not in CATEGORIES: 123 130 raise ValueError(f"Invalid txt_topic. Must be one of {CATEGORIES}") 124 131 MLNode._validate_comparison(comparison) 125 132 MLNode._validate_probability(value) 126 - 127 - return filter_group.add_filter({ 128 - "text_arbitary": [txt_topic, comparison, value] 129 - }) 133 + 134 + return filter_group.add_filter( 135 + {"text_arbitary": [txt_topic, comparison, value]} 136 + )
+27 -18
pasturepy/nodes/text.py
··· 1 - from pasturepy.constants.fields import TEXT_FIELDS, OPTION_FIELDS 1 + from pasturepy.constants.fields import OPTION_FIELDS, TEXT_FIELDS 2 2 from pasturepy.constants.graze_json import REGEX_METHODS, WORD_METHODS 3 3 4 - class TextNode: 5 4 5 + class TextNode: 6 6 @staticmethod 7 7 def _validate_field(field: str) -> None: 8 8 if field not in (TEXT_FIELDS | OPTION_FIELDS): ··· 10 10 f"Invalid text field '{field}'. " 11 11 f"Must be one of: {', '.join(sorted(TEXT_FIELDS | OPTION_FIELDS))}" 12 12 ) 13 - 13 + 14 14 @staticmethod 15 - def word_list(filter_group, method: str, field: str, terms: list, 16 - ignore_case: bool = True, regex_list: bool = False): 15 + def word_list( 16 + filter_group, 17 + method: str, 18 + field: str, 19 + terms: list, 20 + ignore_case: bool = True, 21 + regex_list: bool = False, 22 + ): 17 23 if method not in WORD_METHODS: 18 - raise ValueError(f"Invalid method '{method}'. Must be one of {WORD_METHODS}") 24 + raise ValueError( 25 + f"Invalid method '{method}'. Must be one of {WORD_METHODS}" 26 + ) 19 27 TextNode._validate_field(field) 20 - 21 - return filter_group.add_filter({ 22 - method: [field, terms, ignore_case, regex_list] 23 - }) 24 - 28 + 29 + return filter_group.add_filter( 30 + {method: [field, terms, ignore_case, regex_list]} 31 + ) 32 + 25 33 @staticmethod 26 - def regex(filter_group, method: str, field: str, term: str, 27 - ignore_case: bool = True): 34 + def regex( 35 + filter_group, method: str, field: str, term: str, ignore_case: bool = True 36 + ): 28 37 if method not in REGEX_METHODS: 29 - raise ValueError(f"Invalid method '{method}'. Must be one of {REGEX_METHODS}") 38 + raise ValueError( 39 + f"Invalid method '{method}'. Must be one of {REGEX_METHODS}" 40 + ) 30 41 TextNode._validate_field(field) 31 - 32 - return filter_group.add_filter({ 33 - method: [field, term, ignore_case] 34 - }) 42 + 43 + return filter_group.add_filter({method: [field, term, ignore_case]})
+14 -11
pasturepy/utils/__init__.py
··· 1 - from .combine_regex import ( 2 - load_terms, 3 - normalize_escaping, 4 - deduplicate_terms, 5 - combine_terms_to_regex, 6 - load_and_combine, 1 + from .exceptions import ( 2 + ComplexPatternError, 3 + GrexNotFoundError, 4 + ValidationError, 7 5 ) 6 + from .term_expander import TermExpander 7 + from .grex_optimizer import GrexOptimizer 8 + from .pattern_validator import validate_pattern, validate_with_literals 8 9 9 10 __all__ = [ 10 - "load_terms", 11 - "normalize_escaping", 12 - "deduplicate_terms", 13 - "combine_terms_to_regex", 14 - "load_and_combine", 11 + "ComplexPatternError", 12 + "GrexNotFoundError", 13 + "ValidationError", 14 + "TermExpander", 15 + "GrexOptimizer", 16 + "validate_pattern", 17 + "validate_with_literals", 15 18 ]