Repository files navigation

testscodecovpublishPyPI version

a lib for describing Actions and how they should be performed

Overview

Side effects are annoying. Verification of intended outcome is often difficult and can depend on the system's state at runtime. Questions like "Is the file going to be present when data is written?" or "Will that service be available?" come to mind. Keeping track of external system state is just impractical, but declaring intent and encapsulating its disposition is doable.

Usage

What are Actions for?

Action objects are used to declare intent:

>>>action=Read('path/to/some/file')

The action, above, represents the intent to Read the contents from the file at some path. An Action can be "performed" and the result is captured by a Result object:

>>>result=action.perform() # produces a Result object

The result holds disposition information about the outcome of the action. That includes information like whether or not it was .successful or that it was .produced_at some unix timestamp (microseconds by default). To gain access to the value of the result, check the .value attribute. If unsuccessful, there will be an Exception, otherwise there will be an instance of some non-Exception type.

Can Actions be connected?

A Result can be produced by performing an Action and that value can be percolated through a collection of ActionTypes using the Pipeline abstraction:

>>>pipeline=Pipeline(ReadInput('Which file? '), Read)

The above, is not the most helpful incantation, but toss the following in a while loop and witness some REPL-like behavior (bonus points for feeding it actual filenames/filepaths).

result=Pipeline(ReadInput('Which file? '), Read).perform()
print(result.value)

Sometimes ActionTypes in a Pipeline don't "fit" together. That's where the Pipeline.Fitting comes in:

listen=ReadInput('What should I record? ')
record=Pipeline.Fitting(
action=Write,
**{
'prefix': f'[{datetime.now()}] ',
'append': True,
'filename': filename,
'to_write': Pipeline.Receiver
},
)
Pipeline(listen, record).perform()

⚠️NOTE: Writing to stdout is also possible using the Write.STDOUT object as a filename. How that works is an exercise left for the user.

Handling multiple Actions at a time

An Action collection can be used to describe a procedure:

actions= [action,
Read('path/to/some/other/file'),
ReadInput('>>> how goes? <<<\n > '),
MakeRequest('GET', 'http://google.com'),
RetryPolicy(MakeRequest('GET', 'http://bad-connectivity.com'),
max_retries=2,
delay_between_attempts=2)
Write('path/to/yet/another/file', 'sup')]
procedure=Procedure(actions)

And a Procedure can be executed synchronously or otherwise:

results=procedure.execute() # synchronously by default_results=procedure.execute(synchronously=False) # async; not thread saferesult=next(results)
print(result.value)

A KeyedProcedure is just a Procedure comprised of named Actions. The Action names are used as keys for convenient result lookup.

prompt='>>> sure, I'llsaveitforya.. <<<\n>'saveme = ReadInput(prompt).set(name='saveme')
writeme=Write('path/to/yet/another/file', 'sup').set(name='writeme')
actions= [saveme, writeme]
keyed_procedure=KeyedProcedure(actions)
results=keyed_procedure.execute()
keyed_results=dict(results)
first, second=keyed_results.get('saveme'), keyed_results.get('writeme')

⚠️NOTE:Procedure elements are evaluated independently unlike with a Pipeline in which the result of performing an Action is passed to the next ActionType.

For the honeybadgers

One can also create an Action from some arbitrary function

>>>Call(closure=Closure(some_function, arg, kwarg=kwarg))

Development

Setup

Build scripting is managed via noxfile. Execute nox -l to see the available commands (set the USEVENV environment variable to view virtualenv-oriented commands). To get started, simply run nox. Doing so will install actionpack on your PYTHONPATH. Using the USEVENV environment variable, a virtualenv can be created in the local ".nox/" directory with something like:

USEVENV=virtualenv nox -s actionpack-venv-install-3.10

All tests can be run with nox -s test and a single test can be run with something like the following:

TESTNAME=<tests-subdir>.<test-module>.<class-name>.<method-name> nox -s test

Coverage reports are optional and can be disabled using the COVERAGE environment variable set to a falsy value like "no".

Homebrewed Actions

Making new actionpack.actions is straightforward. After defining a class that inherits Action, ensure it has an .instruction method. If any attribute validation is desired, a .validate method can be added.

There is no need to add Action dependencies to setup.py. Dependencies required for developing an Action go in :::drum roll::: requirements.txt. When declaring your Action class, a requires parameter can be passed a tuple.

classMakeRequest(Action, requires=('requests',)):
...

This will check if the dependencies are installed and, if so, will register each of them as class attributes.

mr=MakeRequest('GET', 'http://localhost')
mr.requests#=> <module 'requests' from '~/actionpack/actionpack-venv/lib/python3/site-packages/requests/__init__.py'>

About

a lib for describing Actions and how they should be performed

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

testscodecovpublishPyPI version

a lib for describing Actions and how they should be performed

Overview

Side effects are annoying. Verification of intended outcome is often difficult and can depend on the system's state at runtime. Questions like "Is the file going to be present when data is written?" or "Will that service be available?" come to mind. Keeping track of external system state is just impractical, but declaring intent and encapsulating its disposition is doable.

Usage

What are Actions for?

Action objects are used to declare intent:

>>>action=Read('path/to/some/file')

The action, above, represents the intent to Read the contents from the file at some path. An Action can be "performed" and the result is captured by a Result object:

>>>result=action.perform() # produces a Result object

The result holds disposition information about the outcome of the action. That includes information like whether or not it was .successful or that it was .produced_at some unix timestamp (microseconds by default). To gain access to the value of the result, check the .value attribute. If unsuccessful, there will be an Exception, otherwise there will be an instance of some non-Exception type.

Can Actions be connected?

A Result can be produced by performing an Action and that value can be percolated through a collection of ActionTypes using the Pipeline abstraction:

>>>pipeline=Pipeline(ReadInput('Which file? '), Read)

The above, is not the most helpful incantation, but toss the following in a while loop and witness some REPL-like behavior (bonus points for feeding it actual filenames/filepaths).

result=Pipeline(ReadInput('Which file? '), Read).perform()
print(result.value)

Sometimes ActionTypes in a Pipeline don't "fit" together. That's where the Pipeline.Fitting comes in:

listen=ReadInput('What should I record? ')
record=Pipeline.Fitting(
action=Write,
**{
'prefix': f'[{datetime.now()}] ',
'append': True,
'filename': filename,
'to_write': Pipeline.Receiver
},
)
Pipeline(listen, record).perform()

⚠️NOTE: Writing to stdout is also possible using the Write.STDOUT object as a filename. How that works is an exercise left for the user.

Handling multiple Actions at a time

An Action collection can be used to describe a procedure:

actions= [action,
Read('path/to/some/other/file'),
ReadInput('>>> how goes? <<<\n > '),
MakeRequest('GET', 'http://google.com'),
RetryPolicy(MakeRequest('GET', 'http://bad-connectivity.com'),
max_retries=2,
delay_between_attempts=2)
Write('path/to/yet/another/file', 'sup')]
procedure=Procedure(actions)

And a Procedure can be executed synchronously or otherwise:

results=procedure.execute() # synchronously by default_results=procedure.execute(synchronously=False) # async; not thread saferesult=next(results)
print(result.value)

A KeyedProcedure is just a Procedure comprised of named Actions. The Action names are used as keys for convenient result lookup.

prompt='>>> sure, I'llsaveitforya.. <<<\n>'saveme = ReadInput(prompt).set(name='saveme')
writeme=Write('path/to/yet/another/file', 'sup').set(name='writeme')
actions= [saveme, writeme]
keyed_procedure=KeyedProcedure(actions)
results=keyed_procedure.execute()
keyed_results=dict(results)
first, second=keyed_results.get('saveme'), keyed_results.get('writeme')

⚠️NOTE:Procedure elements are evaluated independently unlike with a Pipeline in which the result of performing an Action is passed to the next ActionType.

For the honeybadgers

One can also create an Action from some arbitrary function

>>>Call(closure=Closure(some_function, arg, kwarg=kwarg))

Development

Setup

Build scripting is managed via noxfile. Execute nox -l to see the available commands (set the USEVENV environment variable to view virtualenv-oriented commands). To get started, simply run nox. Doing so will install actionpack on your PYTHONPATH. Using the USEVENV environment variable, a virtualenv can be created in the local ".nox/" directory with something like:

USEVENV=virtualenv nox -s actionpack-venv-install-3.10

All tests can be run with nox -s test and a single test can be run with something like the following:

TESTNAME=<tests-subdir>.<test-module>.<class-name>.<method-name> nox -s test

Coverage reports are optional and can be disabled using the COVERAGE environment variable set to a falsy value like "no".

Homebrewed Actions

Making new actionpack.actions is straightforward. After defining a class that inherits Action, ensure it has an .instruction method. If any attribute validation is desired, a .validate method can be added.

There is no need to add Action dependencies to setup.py. Dependencies required for developing an Action go in :::drum roll::: requirements.txt. When declaring your Action class, a requires parameter can be passed a tuple.

classMakeRequest(Action, requires=('requests',)):
...

This will check if the dependencies are installed and, if so, will register each of them as class attributes.

mr=MakeRequest('GET', 'http://localhost')
mr.requests#=> <module 'requests' from '~/actionpack/actionpack-venv/lib/python3/site-packages/requests/__init__.py'>

About

a lib for describing Actions and how they should be performed

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

testscodecovpublishPyPI version

a lib for describing Actions and how they should be performed

Overview

Side effects are annoying. Verification of intended outcome is often difficult and can depend on the system's state at runtime. Questions like "Is the file going to be present when data is written?" or "Will that service be available?" come to mind. Keeping track of external system state is just impractical, but declaring intent and encapsulating its disposition is doable.

Usage

What are Actions for?

Action objects are used to declare intent:

>>>action=Read('path/to/some/file')

The action, above, represents the intent to Read the contents from the file at some path. An Action can be "performed" and the result is captured by a Result object:

>>>result=action.perform() # produces a Result object

The result holds disposition information about the outcome of the action. That includes information like whether or not it was .successful or that it was .produced_at some unix timestamp (microseconds by default). To gain access to the value of the result, check the .value attribute. If unsuccessful, there will be an Exception, otherwise there will be an instance of some non-Exception type.

Can Actions be connected?

A Result can be produced by performing an Action and that value can be percolated through a collection of ActionTypes using the Pipeline abstraction:

>>>pipeline=Pipeline(ReadInput('Which file? '), Read)

The above, is not the most helpful incantation, but toss the following in a while loop and witness some REPL-like behavior (bonus points for feeding it actual filenames/filepaths).

result=Pipeline(ReadInput('Which file? '), Read).perform()
print(result.value)

Sometimes ActionTypes in a Pipeline don't "fit" together. That's where the Pipeline.Fitting comes in:

listen=ReadInput('What should I record? ')
record=Pipeline.Fitting(
action=Write,
**{
'prefix': f'[{datetime.now()}] ',
'append': True,
'filename': filename,
'to_write': Pipeline.Receiver
},
)
Pipeline(listen, record).perform()

⚠️NOTE: Writing to stdout is also possible using the Write.STDOUT object as a filename. How that works is an exercise left for the user.

Handling multiple Actions at a time

An Action collection can be used to describe a procedure:

actions= [action,
Read('path/to/some/other/file'),
ReadInput('>>> how goes? <<<\n > '),
MakeRequest('GET', 'http://google.com'),
RetryPolicy(MakeRequest('GET', 'http://bad-connectivity.com'),
max_retries=2,
delay_between_attempts=2)
Write('path/to/yet/another/file', 'sup')]
procedure=Procedure(actions)

And a Procedure can be executed synchronously or otherwise:

results=procedure.execute() # synchronously by default_results=procedure.execute(synchronously=False) # async; not thread saferesult=next(results)
print(result.value)

A KeyedProcedure is just a Procedure comprised of named Actions. The Action names are used as keys for convenient result lookup.

prompt='>>> sure, I'llsaveitforya.. <<<\n>'saveme = ReadInput(prompt).set(name='saveme')
writeme=Write('path/to/yet/another/file', 'sup').set(name='writeme')
actions= [saveme, writeme]
keyed_procedure=KeyedProcedure(actions)
results=keyed_procedure.execute()
keyed_results=dict(results)
first, second=keyed_results.get('saveme'), keyed_results.get('writeme')

⚠️NOTE:Procedure elements are evaluated independently unlike with a Pipeline in which the result of performing an Action is passed to the next ActionType.

For the honeybadgers

One can also create an Action from some arbitrary function

>>>Call(closure=Closure(some_function, arg, kwarg=kwarg))

Development

Setup

Build scripting is managed via noxfile. Execute nox -l to see the available commands (set the USEVENV environment variable to view virtualenv-oriented commands). To get started, simply run nox. Doing so will install actionpack on your PYTHONPATH. Using the USEVENV environment variable, a virtualenv can be created in the local ".nox/" directory with something like:

USEVENV=virtualenv nox -s actionpack-venv-install-3.10

All tests can be run with nox -s test and a single test can be run with something like the following:

TESTNAME=<tests-subdir>.<test-module>.<class-name>.<method-name> nox -s test

Coverage reports are optional and can be disabled using the COVERAGE environment variable set to a falsy value like "no".

Homebrewed Actions

Making new actionpack.actions is straightforward. After defining a class that inherits Action, ensure it has an .instruction method. If any attribute validation is desired, a .validate method can be added.

There is no need to add Action dependencies to setup.py. Dependencies required for developing an Action go in :::drum roll::: requirements.txt. When declaring your Action class, a requires parameter can be passed a tuple.

classMakeRequest(Action, requires=('requests',)):
...

This will check if the dependencies are installed and, if so, will register each of them as class attributes.

mr=MakeRequest('GET', 'http://localhost')
mr.requests#=> <module 'requests' from '~/actionpack/actionpack-venv/lib/python3/site-packages/requests/__init__.py'>

About

a lib for describing Actions and how they should be performed

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

testscodecovpublishPyPI version

a lib for describing Actions and how they should be performed

Overview

Side effects are annoying. Verification of intended outcome is often difficult and can depend on the system's state at runtime. Questions like "Is the file going to be present when data is written?" or "Will that service be available?" come to mind. Keeping track of external system state is just impractical, but declaring intent and encapsulating its disposition is doable.

Usage

What are Actions for?

Action objects are used to declare intent:

>>>action=Read('path/to/some/file')

The action, above, represents the intent to Read the contents from the file at some path. An Action can be "performed" and the result is captured by a Result object:

>>>result=action.perform() # produces a Result object

The result holds disposition information about the outcome of the action. That includes information like whether or not it was .successful or that it was .produced_at some unix timestamp (microseconds by default). To gain access to the value of the result, check the .value attribute. If unsuccessful, there will be an Exception, otherwise there will be an instance of some non-Exception type.

Can Actions be connected?

A Result can be produced by performing an Action and that value can be percolated through a collection of ActionTypes using the Pipeline abstraction:

>>>pipeline=Pipeline(ReadInput('Which file? '), Read)

The above, is not the most helpful incantation, but toss the following in a while loop and witness some REPL-like behavior (bonus points for feeding it actual filenames/filepaths).

result=Pipeline(ReadInput('Which file? '), Read).perform()
print(result.value)

Sometimes ActionTypes in a Pipeline don't "fit" together. That's where the Pipeline.Fitting comes in:

listen=ReadInput('What should I record? ')
record=Pipeline.Fitting(
action=Write,
**{
'prefix': f'[{datetime.now()}] ',
'append': True,
'filename': filename,
'to_write': Pipeline.Receiver
},
)
Pipeline(listen, record).perform()

⚠️NOTE: Writing to stdout is also possible using the Write.STDOUT object as a filename. How that works is an exercise left for the user.

Handling multiple Actions at a time

An Action collection can be used to describe a procedure:

actions= [action,
Read('path/to/some/other/file'),
ReadInput('>>> how goes? <<<\n > '),
MakeRequest('GET', 'http://google.com'),
RetryPolicy(MakeRequest('GET', 'http://bad-connectivity.com'),
max_retries=2,
delay_between_attempts=2)
Write('path/to/yet/another/file', 'sup')]
procedure=Procedure(actions)

And a Procedure can be executed synchronously or otherwise:

results=procedure.execute() # synchronously by default_results=procedure.execute(synchronously=False) # async; not thread saferesult=next(results)
print(result.value)

A KeyedProcedure is just a Procedure comprised of named Actions. The Action names are used as keys for convenient result lookup.

prompt='>>> sure, I'llsaveitforya.. <<<\n>'saveme = ReadInput(prompt).set(name='saveme')
writeme=Write('path/to/yet/another/file', 'sup').set(name='writeme')
actions= [saveme, writeme]
keyed_procedure=KeyedProcedure(actions)
results=keyed_procedure.execute()
keyed_results=dict(results)
first, second=keyed_results.get('saveme'), keyed_results.get('writeme')

⚠️NOTE:Procedure elements are evaluated independently unlike with a Pipeline in which the result of performing an Action is passed to the next ActionType.

For the honeybadgers

One can also create an Action from some arbitrary function

>>>Call(closure=Closure(some_function, arg, kwarg=kwarg))

Development

Setup

Build scripting is managed via noxfile. Execute nox -l to see the available commands (set the USEVENV environment variable to view virtualenv-oriented commands). To get started, simply run nox. Doing so will install actionpack on your PYTHONPATH. Using the USEVENV environment variable, a virtualenv can be created in the local ".nox/" directory with something like:

USEVENV=virtualenv nox -s actionpack-venv-install-3.10

All tests can be run with nox -s test and a single test can be run with something like the following:

TESTNAME=<tests-subdir>.<test-module>.<class-name>.<method-name> nox -s test

Coverage reports are optional and can be disabled using the COVERAGE environment variable set to a falsy value like "no".

Homebrewed Actions

Making new actionpack.actions is straightforward. After defining a class that inherits Action, ensure it has an .instruction method. If any attribute validation is desired, a .validate method can be added.

There is no need to add Action dependencies to setup.py. Dependencies required for developing an Action go in :::drum roll::: requirements.txt. When declaring your Action class, a requires parameter can be passed a tuple.

classMakeRequest(Action, requires=('requests',)):
...

This will check if the dependencies are installed and, if so, will register each of them as class attributes.

mr=MakeRequest('GET', 'http://localhost')
mr.requests#=> <module 'requests' from '~/actionpack/actionpack-venv/lib/python3/site-packages/requests/__init__.py'>

About

a lib for describing Actions and how they should be performed

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

testscodecovpublishPyPI version

a lib for describing Actions and how they should be performed

Overview

Side effects are annoying. Verification of intended outcome is often difficult and can depend on the system's state at runtime. Questions like "Is the file going to be present when data is written?" or "Will that service be available?" come to mind. Keeping track of external system state is just impractical, but declaring intent and encapsulating its disposition is doable.

Usage

What are Actions for?

Action objects are used to declare intent:

>>>action=Read('path/to/some/file')

The action, above, represents the intent to Read the contents from the file at some path. An Action can be "performed" and the result is captured by a Result object:

>>>result=action.perform() # produces a Result object

The result holds disposition information about the outcome of the action. That includes information like whether or not it was .successful or that it was .produced_at some unix timestamp (microseconds by default). To gain access to the value of the result, check the .value attribute. If unsuccessful, there will be an Exception, otherwise there will be an instance of some non-Exception type.

Can Actions be connected?

A Result can be produced by performing an Action and that value can be percolated through a collection of ActionTypes using the Pipeline abstraction:

>>>pipeline=Pipeline(ReadInput('Which file? '), Read)

The above, is not the most helpful incantation, but toss the following in a while loop and witness some REPL-like behavior (bonus points for feeding it actual filenames/filepaths).

result=Pipeline(ReadInput('Which file? '), Read).perform()
print(result.value)

Sometimes ActionTypes in a Pipeline don't "fit" together. That's where the Pipeline.Fitting comes in:

listen=ReadInput('What should I record? ')
record=Pipeline.Fitting(
action=Write,
**{
'prefix': f'[{datetime.now()}] ',
'append': True,
'filename': filename,
'to_write': Pipeline.Receiver
},
)
Pipeline(listen, record).perform()

⚠️NOTE: Writing to stdout is also possible using the Write.STDOUT object as a filename. How that works is an exercise left for the user.

Handling multiple Actions at a time

An Action collection can be used to describe a procedure:

actions= [action,
Read('path/to/some/other/file'),
ReadInput('>>> how goes? <<<\n > '),
MakeRequest('GET', 'http://google.com'),
RetryPolicy(MakeRequest('GET', 'http://bad-connectivity.com'),
max_retries=2,
delay_between_attempts=2)
Write('path/to/yet/another/file', 'sup')]
procedure=Procedure(actions)

And a Procedure can be executed synchronously or otherwise:

results=procedure.execute() # synchronously by default_results=procedure.execute(synchronously=False) # async; not thread saferesult=next(results)
print(result.value)

A KeyedProcedure is just a Procedure comprised of named Actions. The Action names are used as keys for convenient result lookup.

prompt='>>> sure, I'llsaveitforya.. <<<\n>'saveme = ReadInput(prompt).set(name='saveme')
writeme=Write('path/to/yet/another/file', 'sup').set(name='writeme')
actions= [saveme, writeme]
keyed_procedure=KeyedProcedure(actions)
results=keyed_procedure.execute()
keyed_results=dict(results)
first, second=keyed_results.get('saveme'), keyed_results.get('writeme')

⚠️NOTE:Procedure elements are evaluated independently unlike with a Pipeline in which the result of performing an Action is passed to the next ActionType.

For the honeybadgers

One can also create an Action from some arbitrary function

>>>Call(closure=Closure(some_function, arg, kwarg=kwarg))

Development

Setup

Build scripting is managed via noxfile. Execute nox -l to see the available commands (set the USEVENV environment variable to view virtualenv-oriented commands). To get started, simply run nox. Doing so will install actionpack on your PYTHONPATH. Using the USEVENV environment variable, a virtualenv can be created in the local ".nox/" directory with something like:

USEVENV=virtualenv nox -s actionpack-venv-install-3.10

All tests can be run with nox -s test and a single test can be run with something like the following:

TESTNAME=<tests-subdir>.<test-module>.<class-name>.<method-name> nox -s test

Coverage reports are optional and can be disabled using the COVERAGE environment variable set to a falsy value like "no".

Homebrewed Actions

Making new actionpack.actions is straightforward. After defining a class that inherits Action, ensure it has an .instruction method. If any attribute validation is desired, a .validate method can be added.

There is no need to add Action dependencies to setup.py. Dependencies required for developing an Action go in :::drum roll::: requirements.txt. When declaring your Action class, a requires parameter can be passed a tuple.

classMakeRequest(Action, requires=('requests',)):
...

This will check if the dependencies are installed and, if so, will register each of them as class attributes.

mr=MakeRequest('GET', 'http://localhost')
mr.requests#=> <module 'requests' from '~/actionpack/actionpack-venv/lib/python3/site-packages/requests/__init__.py'>

About

a lib for describing Actions and how they should be performed

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

testscodecovpublishPyPI version

a lib for describing Actions and how they should be performed

Overview

Side effects are annoying. Verification of intended outcome is often difficult and can depend on the system's state at runtime. Questions like "Is the file going to be present when data is written?" or "Will that service be available?" come to mind. Keeping track of external system state is just impractical, but declaring intent and encapsulating its disposition is doable.

Usage

What are Actions for?

Action objects are used to declare intent:

>>>action=Read('path/to/some/file')

The action, above, represents the intent to Read the contents from the file at some path. An Action can be "performed" and the result is captured by a Result object:

>>>result=action.perform() # produces a Result object

The result holds disposition information about the outcome of the action. That includes information like whether or not it was .successful or that it was .produced_at some unix timestamp (microseconds by default). To gain access to the value of the result, check the .value attribute. If unsuccessful, there will be an Exception, otherwise there will be an instance of some non-Exception type.

Can Actions be connected?

A Result can be produced by performing an Action and that value can be percolated through a collection of ActionTypes using the Pipeline abstraction:

>>>pipeline=Pipeline(ReadInput('Which file? '), Read)

The above, is not the most helpful incantation, but toss the following in a while loop and witness some REPL-like behavior (bonus points for feeding it actual filenames/filepaths).

result=Pipeline(ReadInput('Which file? '), Read).perform()
print(result.value)

Sometimes ActionTypes in a Pipeline don't "fit" together. That's where the Pipeline.Fitting comes in:

listen=ReadInput('What should I record? ')
record=Pipeline.Fitting(
action=Write,
**{
'prefix': f'[{datetime.now()}] ',
'append': True,
'filename': filename,
'to_write': Pipeline.Receiver
},
)
Pipeline(listen, record).perform()

⚠️NOTE: Writing to stdout is also possible using the Write.STDOUT object as a filename. How that works is an exercise left for the user.

Handling multiple Actions at a time

An Action collection can be used to describe a procedure:

actions= [action,
Read('path/to/some/other/file'),
ReadInput('>>> how goes? <<<\n > '),
MakeRequest('GET', 'http://google.com'),
RetryPolicy(MakeRequest('GET', 'http://bad-connectivity.com'),
max_retries=2,
delay_between_attempts=2)
Write('path/to/yet/another/file', 'sup')]
procedure=Procedure(actions)

And a Procedure can be executed synchronously or otherwise:

results=procedure.execute() # synchronously by default_results=procedure.execute(synchronously=False) # async; not thread saferesult=next(results)
print(result.value)

A KeyedProcedure is just a Procedure comprised of named Actions. The Action names are used as keys for convenient result lookup.

prompt='>>> sure, I'llsaveitforya.. <<<\n>'saveme = ReadInput(prompt).set(name='saveme')
writeme=Write('path/to/yet/another/file', 'sup').set(name='writeme')
actions= [saveme, writeme]
keyed_procedure=KeyedProcedure(actions)
results=keyed_procedure.execute()
keyed_results=dict(results)
first, second=keyed_results.get('saveme'), keyed_results.get('writeme')

⚠️NOTE:Procedure elements are evaluated independently unlike with a Pipeline in which the result of performing an Action is passed to the next ActionType.

For the honeybadgers

One can also create an Action from some arbitrary function

>>>Call(closure=Closure(some_function, arg, kwarg=kwarg))

Development

Setup

Build scripting is managed via noxfile. Execute nox -l to see the available commands (set the USEVENV environment variable to view virtualenv-oriented commands). To get started, simply run nox. Doing so will install actionpack on your PYTHONPATH. Using the USEVENV environment variable, a virtualenv can be created in the local ".nox/" directory with something like:

USEVENV=virtualenv nox -s actionpack-venv-install-3.10

All tests can be run with nox -s test and a single test can be run with something like the following:

TESTNAME=<tests-subdir>.<test-module>.<class-name>.<method-name> nox -s test

Coverage reports are optional and can be disabled using the COVERAGE environment variable set to a falsy value like "no".

Homebrewed Actions

Making new actionpack.actions is straightforward. After defining a class that inherits Action, ensure it has an .instruction method. If any attribute validation is desired, a .validate method can be added.

There is no need to add Action dependencies to setup.py. Dependencies required for developing an Action go in :::drum roll::: requirements.txt. When declaring your Action class, a requires parameter can be passed a tuple.

classMakeRequest(Action, requires=('requests',)):
...

This will check if the dependencies are installed and, if so, will register each of them as class attributes.

mr=MakeRequest('GET', 'http://localhost')
mr.requests#=> <module 'requests' from '~/actionpack/actionpack-venv/lib/python3/site-packages/requests/__init__.py'>

About

a lib for describing Actions and how they should be performed

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

testscodecovpublishPyPI version

a lib for describing Actions and how they should be performed

Overview

Side effects are annoying. Verification of intended outcome is often difficult and can depend on the system's state at runtime. Questions like "Is the file going to be present when data is written?" or "Will that service be available?" come to mind. Keeping track of external system state is just impractical, but declaring intent and encapsulating its disposition is doable.

Usage

What are Actions for?

Action objects are used to declare intent:

>>>action=Read('path/to/some/file')

The action, above, represents the intent to Read the contents from the file at some path. An Action can be "performed" and the result is captured by a Result object:

>>>result=action.perform() # produces a Result object

The result holds disposition information about the outcome of the action. That includes information like whether or not it was .successful or that it was .produced_at some unix timestamp (microseconds by default). To gain access to the value of the result, check the .value attribute. If unsuccessful, there will be an Exception, otherwise there will be an instance of some non-Exception type.

Can Actions be connected?

A Result can be produced by performing an Action and that value can be percolated through a collection of ActionTypes using the Pipeline abstraction:

>>>pipeline=Pipeline(ReadInput('Which file? '), Read)

The above, is not the most helpful incantation, but toss the following in a while loop and witness some REPL-like behavior (bonus points for feeding it actual filenames/filepaths).

result=Pipeline(ReadInput('Which file? '), Read).perform()
print(result.value)

Sometimes ActionTypes in a Pipeline don't "fit" together. That's where the Pipeline.Fitting comes in:

listen=ReadInput('What should I record? ')
record=Pipeline.Fitting(
action=Write,
**{
'prefix': f'[{datetime.now()}] ',
'append': True,
'filename': filename,
'to_write': Pipeline.Receiver
},
)
Pipeline(listen, record).perform()

⚠️NOTE: Writing to stdout is also possible using the Write.STDOUT object as a filename. How that works is an exercise left for the user.

Handling multiple Actions at a time

An Action collection can be used to describe a procedure:

actions= [action,
Read('path/to/some/other/file'),
ReadInput('>>> how goes? <<<\n > '),
MakeRequest('GET', 'http://google.com'),
RetryPolicy(MakeRequest('GET', 'http://bad-connectivity.com'),
max_retries=2,
delay_between_attempts=2)
Write('path/to/yet/another/file', 'sup')]
procedure=Procedure(actions)

And a Procedure can be executed synchronously or otherwise:

results=procedure.execute() # synchronously by default_results=procedure.execute(synchronously=False) # async; not thread saferesult=next(results)
print(result.value)

A KeyedProcedure is just a Procedure comprised of named Actions. The Action names are used as keys for convenient result lookup.

prompt='>>> sure, I'llsaveitforya.. <<<\n>'saveme = ReadInput(prompt).set(name='saveme')
writeme=Write('path/to/yet/another/file', 'sup').set(name='writeme')
actions= [saveme, writeme]
keyed_procedure=KeyedProcedure(actions)
results=keyed_procedure.execute()
keyed_results=dict(results)
first, second=keyed_results.get('saveme'), keyed_results.get('writeme')

⚠️NOTE:Procedure elements are evaluated independently unlike with a Pipeline in which the result of performing an Action is passed to the next ActionType.

For the honeybadgers

One can also create an Action from some arbitrary function

>>>Call(closure=Closure(some_function, arg, kwarg=kwarg))

Development

Setup

Build scripting is managed via noxfile. Execute nox -l to see the available commands (set the USEVENV environment variable to view virtualenv-oriented commands). To get started, simply run nox. Doing so will install actionpack on your PYTHONPATH. Using the USEVENV environment variable, a virtualenv can be created in the local ".nox/" directory with something like:

USEVENV=virtualenv nox -s actionpack-venv-install-3.10

All tests can be run with nox -s test and a single test can be run with something like the following:

TESTNAME=<tests-subdir>.<test-module>.<class-name>.<method-name> nox -s test

Coverage reports are optional and can be disabled using the COVERAGE environment variable set to a falsy value like "no".

Homebrewed Actions

Making new actionpack.actions is straightforward. After defining a class that inherits Action, ensure it has an .instruction method. If any attribute validation is desired, a .validate method can be added.

There is no need to add Action dependencies to setup.py. Dependencies required for developing an Action go in :::drum roll::: requirements.txt. When declaring your Action class, a requires parameter can be passed a tuple.

classMakeRequest(Action, requires=('requests',)):
...

This will check if the dependencies are installed and, if so, will register each of them as class attributes.

mr=MakeRequest('GET', 'http://localhost')
mr.requests#=> <module 'requests' from '~/actionpack/actionpack-venv/lib/python3/site-packages/requests/__init__.py'>

About

a lib for describing Actions and how they should be performed

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

testscodecovpublishPyPI version

a lib for describing Actions and how they should be performed

Overview

Side effects are annoying. Verification of intended outcome is often difficult and can depend on the system's state at runtime. Questions like "Is the file going to be present when data is written?" or "Will that service be available?" come to mind. Keeping track of external system state is just impractical, but declaring intent and encapsulating its disposition is doable.

Usage

What are Actions for?

Action objects are used to declare intent:

>>>action=Read('path/to/some/file')

The action, above, represents the intent to Read the contents from the file at some path. An Action can be "performed" and the result is captured by a Result object:

>>>result=action.perform() # produces a Result object

The result holds disposition information about the outcome of the action. That includes information like whether or not it was .successful or that it was .produced_at some unix timestamp (microseconds by default). To gain access to the value of the result, check the .value attribute. If unsuccessful, there will be an Exception, otherwise there will be an instance of some non-Exception type.

Can Actions be connected?

A Result can be produced by performing an Action and that value can be percolated through a collection of ActionTypes using the Pipeline abstraction:

>>>pipeline=Pipeline(ReadInput('Which file? '), Read)

The above, is not the most helpful incantation, but toss the following in a while loop and witness some REPL-like behavior (bonus points for feeding it actual filenames/filepaths).

result=Pipeline(ReadInput('Which file? '), Read).perform()
print(result.value)

Sometimes ActionTypes in a Pipeline don't "fit" together. That's where the Pipeline.Fitting comes in:

listen=ReadInput('What should I record? ')
record=Pipeline.Fitting(
action=Write,
**{
'prefix': f'[{datetime.now()}] ',
'append': True,
'filename': filename,
'to_write': Pipeline.Receiver
},
)
Pipeline(listen, record).perform()

⚠️NOTE: Writing to stdout is also possible using the Write.STDOUT object as a filename. How that works is an exercise left for the user.

Handling multiple Actions at a time

An Action collection can be used to describe a procedure:

actions= [action,
Read('path/to/some/other/file'),
ReadInput('>>> how goes? <<<\n > '),
MakeRequest('GET', 'http://google.com'),
RetryPolicy(MakeRequest('GET', 'http://bad-connectivity.com'),
max_retries=2,
delay_between_attempts=2)
Write('path/to/yet/another/file', 'sup')]
procedure=Procedure(actions)

And a Procedure can be executed synchronously or otherwise:

results=procedure.execute() # synchronously by default_results=procedure.execute(synchronously=False) # async; not thread saferesult=next(results)
print(result.value)

A KeyedProcedure is just a Procedure comprised of named Actions. The Action names are used as keys for convenient result lookup.

prompt='>>> sure, I'llsaveitforya.. <<<\n>'saveme = ReadInput(prompt).set(name='saveme')
writeme=Write('path/to/yet/another/file', 'sup').set(name='writeme')
actions= [saveme, writeme]
keyed_procedure=KeyedProcedure(actions)
results=keyed_procedure.execute()
keyed_results=dict(results)
first, second=keyed_results.get('saveme'), keyed_results.get('writeme')

⚠️NOTE:Procedure elements are evaluated independently unlike with a Pipeline in which the result of performing an Action is passed to the next ActionType.

For the honeybadgers

One can also create an Action from some arbitrary function

>>>Call(closure=Closure(some_function, arg, kwarg=kwarg))

Development

Setup

Build scripting is managed via noxfile. Execute nox -l to see the available commands (set the USEVENV environment variable to view virtualenv-oriented commands). To get started, simply run nox. Doing so will install actionpack on your PYTHONPATH. Using the USEVENV environment variable, a virtualenv can be created in the local ".nox/" directory with something like:

USEVENV=virtualenv nox -s actionpack-venv-install-3.10

All tests can be run with nox -s test and a single test can be run with something like the following:

TESTNAME=<tests-subdir>.<test-module>.<class-name>.<method-name> nox -s test

Coverage reports are optional and can be disabled using the COVERAGE environment variable set to a falsy value like "no".

Homebrewed Actions

Making new actionpack.actions is straightforward. After defining a class that inherits Action, ensure it has an .instruction method. If any attribute validation is desired, a .validate method can be added.

There is no need to add Action dependencies to setup.py. Dependencies required for developing an Action go in :::drum roll::: requirements.txt. When declaring your Action class, a requires parameter can be passed a tuple.

classMakeRequest(Action, requires=('requests',)):
...

This will check if the dependencies are installed and, if so, will register each of them as class attributes.

mr=MakeRequest('GET', 'http://localhost')
mr.requests#=> <module 'requests' from '~/actionpack/actionpack-venv/lib/python3/site-packages/requests/__init__.py'>

About

a lib for describing Actions and how they should be performed

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages