obsei commited on
Commit
3c80589
β€’
1 Parent(s): 2c472b1

adding Obsei UI

Browse files
Files changed (5) hide show
  1. README.md +16 -31
  2. app.py +97 -0
  3. config.yaml +343 -0
  4. requirements.txt +2 -0
  5. utils.py +210 -0
README.md CHANGED
@@ -1,37 +1,22 @@
1
- ---
2
- title: Obsei Demo
3
- emoji: πŸ“‰
4
- colorFrom: gray
5
- colorTo: yellow
6
- sdk: streamlit
7
- app_file: app.py
8
- pinned: false
9
- ---
10
 
11
- # Configuration
12
 
13
- `title`: _string_
14
- Display title for the Space
15
 
16
- `emoji`: _string_
17
- Space emoji (emoji-only character allowed)
18
 
19
- `colorFrom`: _string_
20
- Color for Thumbnail gradient (red, yellow, green, blue, indigo, purple, pink, gray)
 
 
 
 
21
 
22
- `colorTo`: _string_
23
- Color for Thumbnail gradient (red, yellow, green, blue, indigo, purple, pink, gray)
24
 
25
- `sdk`: _string_
26
- Can be either `gradio` or `streamlit`
27
-
28
- `sdk_version` : _string_
29
- Only applicable for `streamlit` SDK.
30
- See [doc](https://hf.co/docs/hub/spaces) for more info on supported versions.
31
-
32
- `app_file`: _string_
33
- Path to your main application file (which contains either `gradio` or `streamlit` Python code).
34
- Path is relative to the root of the repository.
35
-
36
- `pinned`: _boolean_
37
- Whether the Space stays on top of your list.
 
1
+ ## Demo UI
 
 
 
 
 
 
 
 
2
 
3
+ This is a minimal UI that can spin up to test Obsei. It's based on streamlit and is very easy to extend for your own use.
4
 
5
+ ![Screenshot](https://raw.githubusercontent.com/obsei/obsei-resources/master/images/obsei-ui-demo.png)
 
6
 
7
+ ## Usage
 
8
 
9
+ ### Option 1: Local
10
+ Execute in this folder:
11
+ ```shell
12
+ pip install -r requirements.txt
13
+ streamlit run ui.py
14
+ ```
15
 
16
+ ### Option 2: Container
 
17
 
18
+ Just run
19
+ ```
20
+ docker run -d --name obesi-ui -p 8501:8501 obsei/obsei-ui-demo
21
+ ```
22
+ You can find the UI at `http://localhost:8501`
 
 
 
 
 
 
 
 
app.py ADDED
@@ -0,0 +1,97 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from utils import *
2
+
3
+ current_path = pathlib.Path(__file__).parent.absolute().as_posix()
4
+ configuration = get_obsei_config(current_path, "config.yaml")
5
+ logo_url = "https://raw.githubusercontent.com/obsei/obsei-resources/master/logos/obsei_200x200.png"
6
+
7
+ st.set_page_config(page_title="Obsei Demo", layout="wide", page_icon=logo_url)
8
+
9
+ st.title("Obsei Demo").markdown(
10
+ get_icon_name("Obsei Demo", logo_url, 60, 35), unsafe_allow_html=True
11
+ )
12
+
13
+ st.error(
14
+ """
15
+ **Note:** Demo run will require some secure information based on source or sink selected,
16
+ if you don't trust this environment please close the app.\n
17
+ Do not share and commit generated file as it may contains secure information.
18
+ """
19
+ )
20
+
21
+ (
22
+ pipeline_col,
23
+ spinner_col,
24
+ execute_col,
25
+ download_python_col,
26
+ download_yaml_col,
27
+ ) = st.columns([2, 2, 1, 1, 1])
28
+
29
+ source_col, analyzer_col, sink_col = st.columns([1, 1, 1])
30
+
31
+ source_list = [k for k in configuration["source"].keys()]
32
+ selected_source = source_col.selectbox("Select Observer", source_list)
33
+
34
+ analyzer_list = [k for k in configuration["analyzer"].keys()]
35
+ selected_analyzer = analyzer_col.selectbox("Select Analyzer", analyzer_list)
36
+
37
+ sink_list = [k for k in configuration["sink"].keys()]
38
+ selected_sink = sink_col.selectbox("Select Informer", sink_list)
39
+
40
+ src_icon = get_icon_name(None, configuration["source"][selected_source]["_icon_"])
41
+ analyzer_icon = get_icon_name(
42
+ None, configuration["analyzer"][selected_analyzer]["_icon_"]
43
+ )
44
+ sink_icon = get_icon_name(None, configuration["sink"][selected_sink]["_icon_"])
45
+ pipeline_col.header("Pipeline").markdown(
46
+ f"**Pipeline:** {src_icon} ➑➑ {analyzer_icon} ➑➑ {sink_icon}",
47
+ unsafe_allow_html=True,
48
+ )
49
+
50
+ src_config = configuration["source"][selected_source]["config"]
51
+ render_config(
52
+ src_config, source_col, configuration["source"][selected_source]["_help_"]
53
+ )
54
+
55
+ analyzer_type_config = configuration["analyzer"][selected_analyzer]
56
+ analyzer_type_list = []
57
+ for k in analyzer_type_config.keys():
58
+ if k != "_icon_":
59
+ analyzer_type_list.append(k)
60
+ selected_analyzer_type = analyzer_col.selectbox(f"analyzer type", analyzer_type_list)
61
+ analyzer_config = None
62
+ if "config" in analyzer_type_config[selected_analyzer_type]:
63
+ analyzer_config = analyzer_type_config[selected_analyzer_type]["config"]
64
+ render_config(
65
+ analyzer_config,
66
+ analyzer_col,
67
+ analyzer_type_config[selected_analyzer_type]["_help_"],
68
+ )
69
+ if len(analyzer_type_config[selected_analyzer_type]["analyzer"]) > 1:
70
+ render_config(
71
+ analyzer_type_config[selected_analyzer_type]["analyzer"], analyzer_col
72
+ )
73
+
74
+ sink_config = configuration["sink"][selected_sink]["config"]
75
+ render_config(sink_config, sink_col, configuration["sink"][selected_sink]["_help_"])
76
+
77
+ generate_config = {
78
+ "source": configuration["source"][selected_source]["source"],
79
+ "source_config": src_config,
80
+ "analyzer_config": analyzer_config,
81
+ "analyzer": analyzer_type_config[selected_analyzer_type]["analyzer"],
82
+ "sink": configuration["sink"][selected_sink]["sink"],
83
+ "sink_config": sink_config,
84
+ }
85
+
86
+ python_code = generate_python(generate_config)
87
+ yaml_code = generate_yaml(generate_config)
88
+
89
+ execute_button = execute_col.button("πŸš€ Execute")
90
+ if execute_button:
91
+ execute_workflow(generate_config, spinner_col)
92
+
93
+ with download_python_col:
94
+ download_button(python_code, "generated-code.py", "🐍 Download (.py)")
95
+
96
+ with download_yaml_col:
97
+ download_button(yaml_code, "generated-config.yaml", "πŸ“– Download (.yaml)")
config.yaml ADDED
@@ -0,0 +1,343 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ source:
2
+ Appstore Scrapper:
3
+ _icon_: "https://raw.githubusercontent.com/obsei/obsei-resources/master/logos/appstore.png"
4
+ _help_:
5
+ - '`app_url` is application url on app store.
6
+ - For example for Xcode - https://apps.apple.com/us/app/xcode/id497799835'
7
+ source:
8
+ _target_: obsei.source.AppStoreScrapperSource
9
+ config:
10
+ _target_: obsei.source.AppStoreScrapperConfig
11
+ app_url: "https://apps.apple.com/us/app/gmail-email-by-google/id422689480"
12
+ lookup_period: "1h"
13
+ max_count: 5
14
+ Playstore Scrapper:
15
+ _icon_: "https://raw.githubusercontent.com/obsei/obsei-resources/master/logos/playstore.png"
16
+ _help_:
17
+ - '`app_url` is application url on play store'
18
+ - 'For example for Gmail - https://play.google.com/store/apps/details?id=com.google.android.gm&hl=en_IN&gl=US'
19
+ source:
20
+ _target_: obsei.source.PlayStoreScrapperSource
21
+ config:
22
+ _target_: obsei.source.PlayStoreScrapperConfig
23
+ app_url: "https://play.google.com/store/apps/details?id=com.google.android.gm&hl=en_IN&gl=US"
24
+ lookup_period: "1h"
25
+ max_count: 5
26
+ Maps Reviews Scrapper:
27
+ _icon_: "https://raw.githubusercontent.com/obsei/obsei-resources/master/logos/google_maps.png"
28
+ _help_:
29
+ - 'Collect `api_key` from https://outscraper.com/'
30
+ - ''
31
+ - 'For `queries` enter google maps urls or place ids, for example'
32
+ - "https://www.google.co.in/maps/place/Taj+Mahal/@27.1751496,78.0399535,17z/data=!4m5!3m4!1s0x39747121d702ff6d:0xdd2ae4803f767dde!8m2!3d27.1751448!4d78.0421422"
33
+ source:
34
+ _target_: obsei.source.OSGoogleMapsReviewsSource
35
+ config:
36
+ _target_: obsei.source.OSGoogleMapsReviewsConfig
37
+ api_key: ''
38
+ queries:
39
+ - "https://www.google.co.in/maps/place/Taj+Mahal/@27.1751496,78.0399535,17z/data=!4m5!3m4!1s0x39747121d702ff6d:0xdd2ae4803f767dde!8m2!3d27.1751448!4d78.0421422"
40
+ number_of_reviews: 5
41
+ Reddit Scrapper:
42
+ _icon_: "https://raw.githubusercontent.com/obsei/obsei-resources/master/logos/reddit.png"
43
+ _help_:
44
+ - 'Reddit subreddit, search etc rss url. For proper url refer following link -'
45
+ - 'https://www.reddit.com/r/pathogendavid/comments/tv8m9/pathogendavids_guide_to_rss_and_reddit/'
46
+ source:
47
+ _target_: obsei.source.RedditScrapperSource
48
+ config:
49
+ _target_: obsei.source.RedditScrapperConfig
50
+ url: 'https://www.reddit.com/r/wallstreetbets/comments/.rss?sort=new'
51
+ lookup_period: "1h"
52
+ Twitter:
53
+ _icon_: "https://raw.githubusercontent.com/obsei/obsei-resources/master/logos/twitter.png"
54
+ _help_:
55
+ - '`query` accept search string, @user or #hashtags also'
56
+ - ''
57
+ - 'Need twitter `consumer key` and `secret`, get it from https://developer.twitter.com/en/apply-for-access'
58
+ source:
59
+ _target_: obsei.source.TwitterSource
60
+ config:
61
+ _target_: obsei.source.TwitterSourceConfig
62
+ query: "@Twitter"
63
+ lookup_period: "1h"
64
+ max_tweets: 10
65
+ cred_info:
66
+ _target_: obsei.source.TwitterCredentials
67
+ consumer_key: ''
68
+ consumer_secret: ''
69
+ Facebook:
70
+ _icon_: "https://raw.githubusercontent.com/obsei/obsei-resources/master/logos/facebook.png"
71
+ _help_:
72
+ - '`page_id` is id of your facebook page'
73
+ - ''
74
+ - 'Need facebook app_id, app_secret and long_term_token. Get it from https://developers.facebook.com/apps/'
75
+ source:
76
+ _target_: obsei.source.FacebookSource
77
+ config:
78
+ _target_: obsei.source.FacebookSourceConfig
79
+ page_id: "110844591144719"
80
+ lookup_period: "1h"
81
+ cred_info:
82
+ _target_: obsei.source.FacebookCredentials
83
+ app_id: ''
84
+ app_secret: ''
85
+ long_term_token: ''
86
+ Email:
87
+ _icon_: "https://raw.githubusercontent.com/obsei/obsei-resources/master/logos/gmail.png"
88
+ _help_:
89
+ - 'List of IMAP servers for most commonly used email providers https://www.systoolsgroup.com/imap/'
90
+ - ''
91
+ - 'Also, if you are using a `Gmail` account then make sure you allow less secure apps on your account'
92
+ - 'https://myaccount.google.com/lesssecureapps?pli=1'
93
+ - 'Also enable IMAP access - https://mail.google.com/mail/u/0/#settings/fwdandpop'
94
+ source:
95
+ _target_: obsei.source.EmailSource
96
+ config:
97
+ _target_: obsei.source.EmailConfig
98
+ imap_server: 'imap.gmail.com'
99
+ cred_info:
100
+ _target_: obsei.source.EmailCredInfo
101
+ username: ''
102
+ password: ''
103
+ lookup_period: "1h"
104
+ Reddit:
105
+ _icon_: "https://raw.githubusercontent.com/obsei/obsei-resources/master/logos/reddit.png"
106
+ _help_:
107
+ - 'Reddit account `username` and `password` require'
108
+ - 'Enter list of `subreddits`'
109
+ source:
110
+ _target_: obsei.source.RedditSource
111
+ config:
112
+ _target_: obsei.source.RedditConfig
113
+ subreddits:
114
+ - 'wallstreetbets'
115
+ cred_info:
116
+ _target_: obsei.source.RedditCredInfo
117
+ username: ''
118
+ password: ''
119
+ lookup_period: "1h"
120
+ Google News:
121
+ _icon_: "https://raw.githubusercontent.com/obsei/obsei-resources/master/logos/googlenews.png"
122
+ _help_:
123
+ - '`fetch_article` use crawler to fetch full article'
124
+ source:
125
+ _target_: obsei.source.GoogleNewsSource
126
+ config:
127
+ _target_: obsei.source.GoogleNewsConfig
128
+ query: "bitcoin"
129
+ max_results: 3
130
+ lookup_period: "1d"
131
+ fetch_article: true
132
+ Website Crawler:
133
+ _icon_: "https://raw.githubusercontent.com/obsei/obsei-resources/master/logos/webcrawler.png"
134
+ _help_:
135
+ - '`package name` can be found at the end of the url of app in play store.'
136
+ - ''
137
+ - 'For example - https://play.google.com/store/apps/details?id=com.google.android.gm&hl=en&gl=US'
138
+ - '`com.google.android.gm` is the `package name` for xcode and `us` is `country`.'
139
+ source:
140
+ _target_: obsei.source.TrafilaturaCrawlerSource
141
+ config:
142
+ _target_: obsei.source.TrafilaturaCrawlerConfig
143
+ urls:
144
+ - 'https://obsei.github.io/obsei/'
145
+ sink:
146
+ Jira:
147
+ _icon_: "https://raw.githubusercontent.com/obsei/obsei-resources/master/logos/jira.png"
148
+ _help_:
149
+ - 'For testing purpose you can start jira server locally'
150
+ - 'Refer https://developer.atlassian.com/server/framework/atlassian-sdk/atlas-run-standalone/'
151
+ - ''
152
+ - 'Provide `server url`, `username` and `password` of the user'
153
+ - ''
154
+ - '`type` of issue to be created, for more information refer -'
155
+ - 'https://support.atlassian.com/jira-cloud-administration/docs/what-are-issue-types/'
156
+ - ''
157
+ - '`project` in which issue to be created, for more information refer -'
158
+ - 'https://support.atlassian.com/jira-software-cloud/docs/what-is-a-jira-software-project/'
159
+ sink:
160
+ _target_: obsei.sink.JiraSink
161
+ config:
162
+ _target_: obsei.sink.JiraSinkConfig
163
+ url: 'http://localhost:2990/jira'
164
+ username: ''
165
+ password: ''
166
+ issue_type:
167
+ name: "Task"
168
+ project:
169
+ key: ""
170
+ Zendesk:
171
+ _icon_: "https://raw.githubusercontent.com/obsei/obsei-resources/master/logos/zendesk.png"
172
+ _help_:
173
+ - 'For custom domain refer http://docs.facetoe.com.au/zenpy.html#custom-domains'
174
+ - 'Provide zendesk `domain`'
175
+ - ''
176
+ - 'Provide `subdomain` if you have one'
177
+ - ''
178
+ - 'Provide zendesk account `email` and `password`'
179
+ sink:
180
+ _target_: obsei.sink.ZendeskSink
181
+ config:
182
+ _target_: obsei.sink.ZendeskSinkConfig
183
+ domain: "zendesk.com"
184
+ subdomain: null
185
+ cred_info:
186
+ _target_: obsei.sink.ZendeskCredInfo
187
+ email: ''
188
+ password: ''
189
+ Slack:
190
+ _icon_: "https://raw.githubusercontent.com/obsei/obsei-resources/master/logos/slack.svg"
191
+ _help_:
192
+ - 'Provide slack bot/app `token`, for more detail refer -'
193
+ - 'https://slack.com/intl/en-de/help/articles/215770388-Create-and-regenerate-API-tokens'
194
+ - ''
195
+ - 'To get `channel id` refer -'
196
+ - 'https://stackoverflow.com/questions/40940327/what-is-the-simplest-way-to-find-a-slack-team-id-and-a-channel-id'
197
+ sink:
198
+ _target_: obsei.sink.SlackSink
199
+ config:
200
+ _target_: obsei.sink.SlackSinkConfig
201
+ slack_token: ''
202
+ channel_id: ''
203
+ Elastic:
204
+ _icon_: "https://raw.githubusercontent.com/obsei/obsei-resources/master/logos/elastic.png"
205
+ _help_:
206
+ - 'For testing purpose you can start Elasticsearch server locally via docker'
207
+ - '`docker run -d --name elasticsearch -p 9200:9200 -e "discovery.type=single-node" elasticsearch:7.9.2`'
208
+ - ''
209
+ - ' Provide server `hostname`, `port` along with `index` to be used'
210
+ sink:
211
+ _target_: obsei.sink.ElasticSearchSink
212
+ config:
213
+ _target_: obsei.sink.ElasticSearchSinkConfig
214
+ host: "localhost"
215
+ port: 9200
216
+ index_name: "test"
217
+ Http:
218
+ _icon_: "https://raw.githubusercontent.com/obsei/obsei-resources/master/logos/http_api.png"
219
+ _help_:
220
+ - 'For testing purpose you can create mock http server via postman, refer -'
221
+ - 'https://learning.postman.com/docs/designing-and-developing-your-api/mocking-data/setting-up-mock/'
222
+ - ''
223
+ - 'Provide http server `url` and `headers`'
224
+ sink:
225
+ _target_: obsei.sink.HttpSink
226
+ config:
227
+ _target_: obsei.sink.HttpSinkConfig
228
+ url: 'https://localhost:8080/api/path'
229
+ headers:
230
+ Content-type: "application/json"
231
+ Logger:
232
+ _icon_: "https://raw.githubusercontent.com/obsei/obsei-resources/master/logos/logger.png"
233
+ _help_: null
234
+ sink:
235
+ _target_: obsei.sink.LoggerSink
236
+ config:
237
+ _target_: obsei.sink.LoggerSinkConfig
238
+
239
+ analyzer:
240
+ Classification:
241
+ _icon_: "https://raw.githubusercontent.com/obsei/obsei-resources/master/logos/classification.png"
242
+ Transformer:
243
+ _help_:
244
+ - 'For supported models refer https://huggingface.co/models?filter=zero-shot-classification'
245
+ - ''
246
+ - 'Provide classification `labels`, two labels "positive" and "negative" are added by default'
247
+ - ''
248
+ - 'Possible device values are `auto` (cuda:0 if available otherwise cpu), `cpu` and `cuda:{id}` (cuda device id)'
249
+ config:
250
+ _target_: obsei.analyzer.ClassificationAnalyzerConfig
251
+ labels:
252
+ - "service"
253
+ - "content"
254
+ - "interface"
255
+ multi_class_classification: true
256
+ analyzer:
257
+ _target_: obsei.analyzer.ZeroShotClassificationAnalyzer
258
+ model_name_or_path: "typeform/mobilebert-uncased-mnli"
259
+ device: "auto"
260
+ Sentiment:
261
+ _icon_: "https://raw.githubusercontent.com/obsei/obsei-resources/master/logos/sentiment.png"
262
+ Vader:
263
+ _help_:
264
+ - 'Vader is less resource hungry dictionary based Vader Sentiment detector'
265
+ analyzer:
266
+ _target_: obsei.analyzer.VaderSentimentAnalyzer
267
+ Transformer:
268
+ _help_:
269
+ - 'For supported models refer https://huggingface.co/models?filter=zero-shot-classification'
270
+ - ''
271
+ - 'Possible device values are `auto` (cuda:0 if available otherwise cpu), `cpu` and `cuda:{id}` (cuda device id)'
272
+ config:
273
+ _target_: obsei.analyzer.TransformersSentimentAnalyzerConfig
274
+ labels:
275
+ - "positive"
276
+ - "negative"
277
+ multi_class_classification: false
278
+ analyzer:
279
+ _target_: obsei.analyzer.TransformersSentimentAnalyzer
280
+ model_name_or_path: "typeform/mobilebert-uncased-mnli"
281
+ device: "auto"
282
+ Named Entity Recognition:
283
+ _icon_: "https://raw.githubusercontent.com/obsei/obsei-resources/master/logos/ner.png"
284
+ Transformer:
285
+ _help_:
286
+ - 'For supported models refer https://huggingface.co/models?filter=token-classification'
287
+ - ''
288
+ - 'Possible device values are `auto` (cuda:0 if available otherwise cpu), `cpu` and `cuda:{id}` (cuda device id)'
289
+ analyzer:
290
+ _target_: obsei.analyzer.TransformersNERAnalyzer
291
+ model_name_or_path: "elastic/distilbert-base-cased-finetuned-conll03-english"
292
+ device: "auto"
293
+ Spacy:
294
+ _help_:
295
+ - 'For supported models refer https://spacy.io/models'
296
+ analyzer:
297
+ _target_: obsei.analyzer.SpacyNERAnalyzer
298
+ model_name_or_path: "en_core_web_sm"
299
+ Translation:
300
+ _icon_: "https://raw.githubusercontent.com/obsei/obsei-resources/master/logos/translator.png"
301
+ Transformer:
302
+ _help_:
303
+ - 'For supported models refer https://huggingface.co/models?pipeline_tag=translation'
304
+ - ''
305
+ - 'Possible device values are `auto` (cuda:0 if available otherwise cpu), `cpu` and `cuda:{id}` (cuda device id)'
306
+ analyzer:
307
+ _target_: obsei.analyzer.TranslationAnalyzer
308
+ model_name_or_path: "Helsinki-NLP/opus-mt-en-hi"
309
+ device: "auto"
310
+ PII Anonymizer:
311
+ _icon_: "https://raw.githubusercontent.com/obsei/obsei-resources/master/logos/pii.png"
312
+ Presidio:
313
+ _help_:
314
+ - '`analyze_only` decide whether to return only pii analysis or anonymize text'
315
+ - ''
316
+ - '`return_decision_process` decide whether to return detail information about anonymization decision'
317
+ - ''
318
+ - 'For `nlp_engine_name` spacy and stanza nlp engines are supported, For more info refer -'
319
+ - 'https://microsoft.github.io/presidio/analyzer/developing_recognizers/#utilize-spacy-or-stanza'
320
+ - ''
321
+ - 'Provide `model_name` and `lang_code` of the model'
322
+ config:
323
+ _target_: obsei.analyzer.PresidioPIIAnalyzerConfig
324
+ analyze_only: false
325
+ return_decision_process: false
326
+ analyzer:
327
+ _target_: obsei.analyzer.PresidioPIIAnalyzer
328
+ engine_config:
329
+ _target_: obsei.analyzer.PresidioEngineConfig
330
+ nlp_engine_name: "spacy"
331
+ models:
332
+ - _target_: obsei.analyzer.PresidioModelConfig
333
+ model_name: "en_core_web_md"
334
+ lang_code: "en"
335
+ Dummy:
336
+ _icon_: "https://raw.githubusercontent.com/obsei/obsei-resources/master/logos/dummy.png"
337
+ Dummy:
338
+ _help_:
339
+ - 'Dummy Analyzer, do nothing it simply used for transforming input to output'
340
+ config:
341
+ _target_: obsei.analyzer.DummyAnalyzerConfig
342
+ analyzer:
343
+ _target_: obsei.analyzer.DummyAnalyzer
requirements.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ git+git://github.com/obsei/obsei@master#egg=obsei
2
+ streamlit
utils.py ADDED
@@ -0,0 +1,210 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import base64
2
+ import logging
3
+ import pathlib
4
+ import re
5
+ import sys
6
+ import uuid
7
+
8
+ import streamlit as st
9
+ import yaml
10
+
11
+ from obsei.configuration import ObseiConfiguration
12
+
13
+ logger = logging.getLogger(__name__)
14
+ logging.basicConfig(stream=sys.stdout, level=logging.INFO)
15
+
16
+
17
+ def img_to_bytes(img_path):
18
+ img_bytes = pathlib.Path(img_path).read_bytes()
19
+ encoded = base64.b64encode(img_bytes).decode()
20
+ return encoded
21
+
22
+
23
+ # Copied from https://github.com/jrieke/traingenerator/blob/main/app/utils.py
24
+ def download_button(
25
+ object_to_download, download_filename, button_text # , pickle_it=False
26
+ ):
27
+ try:
28
+ # some strings <-> bytes conversions necessary here
29
+ b64 = base64.b64encode(object_to_download.encode()).decode()
30
+ except AttributeError as e:
31
+ b64 = base64.b64encode(object_to_download).decode()
32
+
33
+ button_uuid = str(uuid.uuid4()).replace("-", "")
34
+ button_id = re.sub("\d+", "", button_uuid)
35
+
36
+ custom_css = f"""
37
+ <style>
38
+ #{button_id} {{
39
+ display: inline-flex;
40
+ align-items: center;
41
+ justify-content: center;
42
+ background-color: rgb(255, 255, 255);
43
+ color: rgb(38, 39, 48);
44
+ padding: .25rem .75rem;
45
+ position: relative;
46
+ text-decoration: none;
47
+ border-radius: 4px;
48
+ border-width: 1px;
49
+ border-style: solid;
50
+ border-color: rgb(230, 234, 241);
51
+ border-image: initial;
52
+ }}
53
+ #{button_id}:hover {{
54
+ border-color: rgb(246, 51, 102);
55
+ color: rgb(246, 51, 102);
56
+ }}
57
+ #{button_id}:active {{
58
+ box-shadow: none;
59
+ background-color: rgb(246, 51, 102);
60
+ color: white;
61
+ }}
62
+ </style> """
63
+
64
+ dl_link = (
65
+ custom_css
66
+ + f'<a download="{download_filename}" id="{button_id}" href="data:file/txt;base64,{b64}">{button_text}</a><br><br>'
67
+ )
68
+ # dl_link = f'<a download="{download_filename}" id="{button_id}" href="data:file/txt;base64,{b64}"><input type="button" kind="primary" value="{button_text}"></a><br></br>'
69
+
70
+ st.markdown(dl_link, unsafe_allow_html=True)
71
+
72
+
73
+ def get_obsei_config(current_path, file_name):
74
+ return ObseiConfiguration(
75
+ config_path=current_path,
76
+ config_filename=file_name,
77
+ ).configuration
78
+
79
+
80
+ @st.cache
81
+ def get_icon_name(name, icon, icon_size=40, font_size=1):
82
+ if not name:
83
+ return f'<img style="vertical-align:middle;margin:5px 5px" src="{icon}" width="{icon_size}" height="{icon_size}">'
84
+ return (
85
+ f'<p style="font-size:{font_size}px">'
86
+ f'<img style="vertical-align:middle;margin:1px 5px" src="{icon}" width="{icon_size}" height="{icon_size}">'
87
+ f"{name}</p>"
88
+ )
89
+
90
+
91
+ def render_config(config, component, help_str=None, parent_key=None):
92
+ if config is None:
93
+ return
94
+
95
+ prefix = "" if parent_key is None else f"{parent_key}."
96
+ if help_str is not None:
97
+ with component.expander("Info", False):
98
+ help_area = "\n".join(help_str)
99
+ st.code(f"{help_area}")
100
+ for k, v in config.items():
101
+ if k == "_target_":
102
+ continue
103
+
104
+ if isinstance(v, dict):
105
+ render_config(v, component, None, k)
106
+ elif isinstance(v, list):
107
+ if len(v) == 0:
108
+ continue
109
+ is_object = isinstance(v[0], dict)
110
+ if is_object:
111
+ for idx, sub_element in enumerate(v):
112
+ render_config(sub_element, component, None, f"{k}[{idx}]")
113
+ else:
114
+ text_data = component.text_area(
115
+ f"{prefix}{k}", ", ".join(v), help="Comma separated list"
116
+ )
117
+ text_list = text_data.split(",")
118
+ config[k] = [text.strip() for text in text_list]
119
+ elif isinstance(v, bool):
120
+ options = [True, False]
121
+ selected_option = component.radio(f"{prefix}{k}", options, options.index(v))
122
+ config[k] = bool(selected_option)
123
+ else:
124
+ tokens = k.split("_")
125
+ is_secret = tokens[-1] in ["key", "password", "token", "secret"]
126
+ hint = (
127
+ "Enter value"
128
+ if "lookup" not in tokens
129
+ else "Format: `<number><d|h|m>` d=day, h=hour & m=minute"
130
+ )
131
+ config[k] = component.text_input(
132
+ f"{prefix}{k}",
133
+ v,
134
+ type="password" if is_secret else "default",
135
+ help=hint,
136
+ )
137
+
138
+
139
+ def generate_python(generate_config):
140
+ return f"""
141
+ import json
142
+
143
+ from obsei.configuration import ObseiConfiguration
144
+
145
+ # This is Obsei workflow path and filename
146
+ config_path = "./"
147
+ config_filename = "workflow.yml"
148
+
149
+ # Extract config via yaml file using `config_path` and `config_filename`
150
+ obsei_configuration = ObseiConfiguration(config_path=config_path, config_filename=config_filename)
151
+
152
+ # Initialize objects using configuration
153
+ source_config = obsei_configuration.initialize_instance("source_config")
154
+ source = obsei_configuration.initialize_instance("source")
155
+ analyzer = obsei_configuration.initialize_instance("analyzer")
156
+ analyzer_config = obsei_configuration.initialize_instance("analyzer_config")
157
+ sink_config = obsei_configuration.initialize_instance("sink_config")
158
+ sink = obsei_configuration.initialize_instance("sink")
159
+
160
+ # This will fetch information from configured source ie twitter, app store etc
161
+ source_response_list = source.lookup(source_config)
162
+
163
+ # This will execute analyzer (Sentiment, classification etc) on source data with provided analyzer_config
164
+ # Analyzer will it's output to `segmented_data` inside `analyzer_response`
165
+ analyzer_response_list = analyzer.analyze_input(
166
+ source_response_list=source_response_list,
167
+ analyzer_config=analyzer_config
168
+ )
169
+
170
+ # This will send analyzed output to configure sink ie Slack, Zendesk etc
171
+ sink_response_list = sink.send_data(analyzer_response_list, sink_config)
172
+ """
173
+
174
+
175
+ def generate_yaml(generate_config):
176
+ return yaml.dump(generate_config)
177
+
178
+
179
+ def execute_workflow(generate_config, component=None):
180
+ progress_show = None
181
+ if component:
182
+ progress_show = component.empty()
183
+ progress_show.code("πŸ„πŸ„πŸ„ Processing 🐒🐒🐒")
184
+ try:
185
+ obsei_configuration = ObseiConfiguration(configuration=generate_config)
186
+
187
+ source_config = obsei_configuration.initialize_instance("source_config")
188
+ source = obsei_configuration.initialize_instance("source")
189
+
190
+ analyzer = obsei_configuration.initialize_instance("analyzer")
191
+ analyzer_config = obsei_configuration.initialize_instance("analyzer_config")
192
+
193
+ sink_config = obsei_configuration.initialize_instance("sink_config")
194
+ sink = obsei_configuration.initialize_instance("sink")
195
+
196
+ source_response_list = source.lookup(source_config)
197
+
198
+ analyzer_response_list = analyzer.analyze_input(
199
+ source_response_list=source_response_list, analyzer_config=analyzer_config
200
+ )
201
+
202
+ sink_response_list = sink.send_data(analyzer_response_list, sink_config)
203
+
204
+ if progress_show:
205
+ progress_show.code("πŸŽ‰πŸŽ‰πŸŽ‰ Processing Complete!! 🍾🍾🍾")
206
+ except Exception as ex:
207
+ if progress_show:
208
+ progress_show.code(f"❗❗❗ Processing Failed!! 😞😞😞 \n πŸ‘‰ ({str(ex)})")
209
+
210
+ raise ex