Finished dev [unoptimized + debuginfo] target(s) in 6.32s
$ cargo build
Compiling hello_world v0.1.0 (.../hello_world)
Finished dev [unoptimized + debuginfo] target(s) in 6.32s
$ cargo build
Compiling hello_world v0.1.0 (.../hello_world)
Finished dev [unoptimized + debuginfo] target(s) in 6.32s
Or build a production ready version
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
$ cargo build --release<br>Finished release [optimized] target(s) in 0.19s
$ cargo build --release<br>Finished release [optimized] target(s) in 0.19s
$ cargo build --release<br>Finished release [optimized] target(s) in 0.19s
Run your app
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
$ cargo run
Finished dev [unoptimized + debuginfo] target(s) in 0.04s
Running `target/debug/hello_world
Hello, world!
$ cargo run
Finished dev [unoptimized + debuginfo] target(s) in 0.04s
Running `target/debug/hello_world
Hello, world!
$ cargo run
Finished dev [unoptimized + debuginfo] target(s) in 0.04s
Running `target/debug/hello_world
Hello, world!
Add functionality to your app
Add Dependencies
Let’s add a dependency to our application. You can find all sorts of libraries on crates.io, the package registry for Rust. In Rust, we often refer to packages as “crates.”
In this project, we’ll use a crate called ferris-says.
In our Cargo.toml file we’ll add this information (that we got from the crate page):
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
[dependencies]
ferris-says = "0.1"
[dependencies]
ferris-says = "0.1"
[dependencies]
ferris-says = "0.1"
Modify main source
Now let’s write a small application with our new dependency. In our main.rs, add the following code:
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
useferris_says::say; // from the previous step
usestd::io::{stdout, BufWriter};
fnmain(){
let stdout = stdout();
let message = String::from("Hello fellow Rustaceans!");
use ferris_says::say; // from the previous step
use std::io::{stdout, BufWriter};
fn main() {
let stdout = stdout();
let message = String::from("Hello fellow Rustaceans!");
let width = message.chars().count();
let mut writer = BufWriter::new(stdout.lock());
say(message.as_bytes(), width, &mut writer).unwrap();
}
use ferris_says::say; // from the previous step
use std::io::{stdout, BufWriter};
fn main() {
let stdout = stdout();
let message = String::from("Hello fellow Rustaceans!");
let width = message.chars().count();
let mut writer = BufWriter::new(stdout.lock());
say(message.as_bytes(), width, &mut writer).unwrap();
}
Run App
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
$ cargo build
Updating crates.io index
Downloaded object v0.20.0
Downloaded textwrap v0.11.0
Downloaded adler v0.2.3
Downloaded ansi_term v0.11.0
Downloaded miniz_oxide v0.4.1
Downloaded gimli v0.22.0
Downloaded strsim v0.8.0
Downloaded error-chain v0.10.0
Downloaded vec_map v0.8.2
Downloaded clap v2.33.3
Downloaded smallvec v0.4.5
Downloaded ferris-says v0.1.2
Downloaded backtrace v0.3.50
Downloaded rustc-demangle v0.1.16
Downloaded addr2line v0.13.0
Downloaded 15crates(1.4 MB)in1.65s
Compiling libc v0.2.76
Compiling bitflags v1.2.1
Compiling gimli v0.22.0
Compiling adler v0.2.3
Compiling rustc-demangle v0.1.16
Compiling unicode-width v0.1.8
Compiling object v0.20.0
Compiling cfg-if v0.1.10
Compiling strsim v0.8.0
Compiling vec_map v0.8.2
Compiling ansi_term v0.11.0
Compiling smallvec v0.4.5
Compiling textwrap v0.11.0
Compiling miniz_oxide v0.4.1
Compiling addr2line v0.13.0
Compiling atty v0.2.14
Compiling backtrace v0.3.50
Compiling clap v2.33.3
Compiling error-chain v0.10.0
Compiling ferris-says v0.1.2
Compiling hello_world v0.1.0(.../hello_world)
Finished dev [unoptimized + debuginfo]target(s)in14.73s
Starten Sie die Datenbank in einem eigenen Fenster mit dem nachfolgenden Kommando:
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
docker compose up
[+] Running 14/14
- db Pulled
- b4d181a07f80 Already exists
- 46ca1d02c28c Pull complete
- a756866b5565 Pull complete
- 36c49e539e90 Pull complete
- 664019fbcaff Pull complete
- 727aeee9c480 Pull complete
- 796589e6b223 Pull complete
- 6664992e747d Pull complete
- 0f933aa7ccec Pull complete
- 99b5e5d88b32 Pull complete
- a901b82e6004 Pull complete
- 625fd35fd0f3 Pull complete
- 9e37bf358a5d Pull complete
[+] Running 1/1
- Container elixis_postgres Started
Attaching to elixis_postgres
elixis_postgres | The files belonging to this database system will be owned by user "postgres".
elixis_postgres |This user must also own the server process.
...
...
...
elixis_postgres |2021-07-1215:01:08.042 UTC [1] LOG: database system is ready to accept connections
docker compose up
[+] Running 14/14
- db Pulled
- b4d181a07f80 Already exists
- 46ca1d02c28c Pull complete
- a756866b5565 Pull complete
- 36c49e539e90 Pull complete
- 664019fbcaff Pull complete
- 727aeee9c480 Pull complete
- 796589e6b223 Pull complete
- 6664992e747d Pull complete
- 0f933aa7ccec Pull complete
- 99b5e5d88b32 Pull complete
- a901b82e6004 Pull complete
- 625fd35fd0f3 Pull complete
- 9e37bf358a5d Pull complete
[+] Running 1/1
- Container elixis_postgres Started
Attaching to elixis_postgres
elixis_postgres | The files belonging to this database system will be owned by user "postgres".
elixis_postgres | This user must also own the server process.
...
...
...
elixis_postgres | 2021-07-12 15:01:08.042 UTC [1] LOG: database system is ready to accept connections
docker compose up
[+] Running 14/14
- db Pulled
- b4d181a07f80 Already exists
- 46ca1d02c28c Pull complete
- a756866b5565 Pull complete
- 36c49e539e90 Pull complete
- 664019fbcaff Pull complete
- 727aeee9c480 Pull complete
- 796589e6b223 Pull complete
- 6664992e747d Pull complete
- 0f933aa7ccec Pull complete
- 99b5e5d88b32 Pull complete
- a901b82e6004 Pull complete
- 625fd35fd0f3 Pull complete
- 9e37bf358a5d Pull complete
[+] Running 1/1
- Container elixis_postgres Started
Attaching to elixis_postgres
elixis_postgres | The files belonging to this database system will be owned by user "postgres".
elixis_postgres | This user must also own the server process.
...
...
...
elixis_postgres | 2021-07-12 15:01:08.042 UTC [1] LOG: database system is ready to accept connections
Datenbanktabellen erstellen
Festlegen der Datenbank-Verbindungsparameter in der Datei
config/dev.exs
config/dev.exs.
Wir verwenden dabie die gleichen Werte, die wir in der Datei
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
Written by Keisuke Nishida, Roger While, Ron Norman, Simon Sobisch, Edward Hart
Built Oct 15201914:14:21
Packaged Sep 06201718:48:43 UTC
C version "4.2.1 Compatible Apple LLVM 11.0.0 (clang-1100.0.33.8)"
cobc --version
cobc (GnuCOBOL) 2.2.0
Copyright (C) 2017 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
Written by Keisuke Nishida, Roger While, Ron Norman, Simon Sobisch, Edward Hart
Built Oct 15 2019 14:14:21
Packaged Sep 06 2017 18:48:43 UTC
C version "4.2.1 Compatible Apple LLVM 11.0.0 (clang-1100.0.33.8)"
cobc --version
cobc (GnuCOBOL) 2.2.0
Copyright (C) 2017 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
Written by Keisuke Nishida, Roger While, Ron Norman, Simon Sobisch, Edward Hart
Built Oct 15 2019 14:14:21
Packaged Sep 06 2017 18:48:43 UTC
C version "4.2.1 Compatible Apple LLVM 11.0.0 (clang-1100.0.33.8)"
First Steps
Create sample programm
Create Hello World programm hello_world.cob
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
HELLO * HISTORIC EXAMPLE OF HELLO WORLD IN COBOL
IDENTIFICATION DIVISION.
PROGRAM-ID. HELLO.
PROCEDURE DIVISION.
DISPLAY "HELLO, WORLD".
STOP RUN.
HELLO * HISTORIC EXAMPLE OF HELLO WORLD IN COBOL
IDENTIFICATION DIVISION.
PROGRAM-ID. HELLO.
PROCEDURE DIVISION.
DISPLAY "HELLO, WORLD".
STOP RUN.
HELLO * HISTORIC EXAMPLE OF HELLO WORLD IN COBOL
IDENTIFICATION DIVISION.
PROGRAM-ID. HELLO.
PROCEDURE DIVISION.
DISPLAY "HELLO, WORLD".
STOP RUN.
Using Jenkins as an automation server for your development, you can automate such repeating tasks as testing and deploying your app.
Starting with a sample Groovy App (a simple calculator) with tests, you will learn how to integrate your app in Jenkins and build a pipeline, so that Jenkins runs the desired tasks every time, you change the code.
You should clone the demo repository into you demo account, because you may change some file during this post., and you will not get write permissions for the demo repository.
Also, clone the repository to your local machine to see what our demo app looks like.
Jenkinsfile README.md bin build.gradle gradlew src
Makefile SampleCalculator build gradle settings.gradle</pre><h4 class="wp-block-heading">The first task, Jenkins will doin our pipeline: build your app</h4><pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">$ ./gradlew build</pre><p>Because it’s the first time you start <code>gradlew</code>, the required software will be downloaded:</p><p>First: the current Gradle Version (Gradle is the Build Tool used by Groovy Projects)</p><pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">Downloading https://services.gradle.org/distributions/gradle-6.2.1-bin.zip
………10
Welcome to Gradle 6.2.1!
Here are the highlights of this release:
- Dependency checksum and signature verification
- Shareable read-only dependency cache
- Documentation links in deprecation messages
For more details see https://docs.gradle.org/6.2.1/release-notes.html
Starting a Gradle Daemon, 2 stopped Daemons could not be reused, use --status for details</pre><h4 class="wp-block-heading">After this, your app will be tested</h4><pre class="EnlighterJSRAW" data-enlighter-language="ini" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">> Task :test
Calculator02Spec > two plus two should equal four PASSED
Calculator01Spec > add: 2 + 3 PASSED
Calculator01Spec > subtract: 4 - 3 PASSED
Calculator01Spec > multiply: 2 * 3 PASSED
BUILD SUCCESSFUL in 34s
5 actionable tasks: 5 executed</pre><h4 class="wp-block-heading">Perform the build again</h4><p>No download is required. The build is much quicker.</p><pre class="EnlighterJSRAW" data-enlighter-language="shell" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">$ ./gradlew build
BUILD SUCCESSFUL in 1s
5 actionable tasks: 5 up-to-date</pre><h4 class="wp-block-heading">Now, test our app:</h4><pre class="EnlighterJSRAW" data-enlighter-language="shell" data-enlighter-theme="" data-enlighter-highlight="1" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">./gradlew clean test
> Task :test
Calculator02Spec > two plus two should equal four PASSED
Calculator01Spec > add: 2 + 3 PASSED
Calculator01Spec > subtract: 4 - 3 PASSED
Calculator01Spec > multiply: 2 * 3 PASSED
BUILD SUCCESSFUL in 4s
5 actionable tasks: 5 executed</pre><h2 class="wp-block-heading">Create a Jenkins Pipeline</h2><p>Start by clicking on the BlueOcean menu item.</p><div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-1 wp-block-columns-is-layout-flex"><div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:33.33%"><figure class="wp-block-image size-large"><img decoding="async" width="500" height="775" src="data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20500%20775'%3E%3C/svg%3E" data-src="https://blog.via-internet.de/wp-content/uploads/2020/03/10_blueocean_menuitem-1.png" alt="" class="wp-image-6230 lazy" data-srcset="https://via-internet.de/blog/wp-content/uploads/2020/03/10_blueocean_menuitem-1.png 500w, https://via-internet.de/blog/wp-content/uploads/2020/03/10_blueocean_menuitem-1-194x300.png 194w" data-sizes="auto, (max-width: 500px) 100vw, 500px"></figure></div><div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:66.66%"><p><strong>Hint:</strong> Blue Ocean is not installed with the default Jenkins installation.</p><p>You have to install the corresponding Plugins.</p><p>Select <code>Manage Jenkins</code> → <code>Manage Plugins</code>.</p><p>Then, select the tab <code>Available</code> and enter in the <code>Filter</code> box: Blue Ocean.</p><p>Install all plugins, that will be listed.</p></div></div><p>Next: Click on the New Pipeline to create your first Pipeline</p><figure class="wp-block-image alignwide size-large"><img decoding="async" width="2040" height="266" src="data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%202040%20266'%3E%3C/svg%3E" data-src="https://i2.wp.com/blog.via-internet.de/wp-content/uploads/2020/03/11_create_pipeline_01-1.png?fit=700%2C91&ssl=1" alt="" class="wp-image-6233 lazy" data-srcset="https://via-internet.de/blog/wp-content/uploads/2020/03/11_create_pipeline_01-1.png 2040w, https://via-internet.de/blog/wp-content/uploads/2020/03/11_create_pipeline_01-1-300x39.png 300w, https://via-internet.de/blog/wp-content/uploads/2020/03/11_create_pipeline_01-1-1024x134.png 1024w, https://via-internet.de/blog/wp-content/uploads/2020/03/11_create_pipeline_01-1-768x100.png 768w, https://via-internet.de/blog/wp-content/uploads/2020/03/11_create_pipeline_01-1-1536x200.png 1536w" data-sizes="auto, (max-width: 2040px) 100vw, 2040px"></figure><p>Use the Item GitHub to specify, where our code is stored</p><figure class="wp-block-image alignwide size-large"><img decoding="async" width="2038" height="908" src="data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%202038%20908'%3E%3C/svg%3E" data-src="https://i2.wp.com/blog.via-internet.de/wp-content/uploads/2020/03/11_create_pipeline_02-1.png?fit=700%2C312&ssl=1" alt="" class="wp-image-6234 lazy" data-srcset="https://via-internet.de/blog/wp-content/uploads/2020/03/11_create_pipeline_02-1.png 2038w, https://via-internet.de/blog/wp-content/uploads/2020/03/11_create_pipeline_02-1-300x134.png 300w, https://via-internet.de/blog/wp-content/uploads/2020/03/11_create_pipeline_02-1-1024x456.png 1024w, https://via-internet.de/blog/wp-content/uploads/2020/03/11_create_pipeline_02-1-768x342.png 768w, https://via-internet.de/blog/wp-content/uploads/2020/03/11_create_pipeline_02-1-1536x684.png 1536w" data-sizes="auto, (max-width: 2038px) 100vw, 2038px"></figure><p>Next, use your GitHub account.</p><p>Be sure, that you cloned the <a href="https://github.com/jenkins-toolbox/SampleApp_GroovyCalculator">demo repository</a></p><figure class="wp-block-image alignwide size-large"><img decoding="async" src="data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%201%201'%3E%3C/svg%3E" data-src="https://blog.via-internet.de/wp-content/uploads/2020/03/11_create_pipeline_03-700x450.png" alt="" class="wp-image-6200 lazy"></figure><p>Next, we select the demo repository <a href="https://github.com/jenkins-toolbox/SampleApp_GroovyCalculator">SampleApp_GroovyCalculator</a></p><figure class="wp-block-image alignwide size-large"><img decoding="async" src="data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%201%201'%3E%3C/svg%3E" data-src="https://blog.via-internet.de/wp-content/uploads/2020/03/11_create_pipeline_04-700x708.png" alt="" class="wp-image-6201 lazy"></figure><p>Click on Create Pipeline and after a few seconds, the pipeline is created.</p><figure class="wp-block-image alignwide size-large"><img decoding="async" src="data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%201%201'%3E%3C/svg%3E" data-src="https://blog.via-internet.de/wp-content/uploads/2020/03/11_create_pipeline_06-700x732.png" alt="" class="wp-image-6203 lazy"></figure><p>Immediately after creating the pipeline, Jenkins is starting the pipeline and all steps included.</p><figure class="wp-block-image alignwide size-large"><img decoding="async" width="2371" height="524" src="data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%202371%20524'%3E%3C/svg%3E" data-src="https://i0.wp.com/blog.via-internet.de/wp-content/uploads/2020/03/22_run_pipeline_1.png?fit=700%2C155&ssl=1" alt="" class="wp-image-6205 lazy" data-srcset="https://via-internet.de/blog/wp-content/uploads/2020/03/22_run_pipeline_1.png 2371w, https://via-internet.de/blog/wp-content/uploads/2020/03/22_run_pipeline_1-300x66.png 300w, https://via-internet.de/blog/wp-content/uploads/2020/03/22_run_pipeline_1-1024x226.png 1024w, https://via-internet.de/blog/wp-content/uploads/2020/03/22_run_pipeline_1-768x170.png 768w, https://via-internet.de/blog/wp-content/uploads/2020/03/22_run_pipeline_1-1536x339.png 1536w, https://via-internet.de/blog/wp-content/uploads/2020/03/22_run_pipeline_1-2048x453.png 2048w" data-sizes="auto, (max-width: 2371px) 100vw, 2371px"></figure><p>If everything went well, you see a positive status</p><figure class="wp-block-image alignwide size-large"><img decoding="async" width="2368" height="504" src="data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%202368%20504'%3E%3C/svg%3E" data-src="https://i1.wp.com/blog.via-internet.de/wp-content/uploads/2020/03/22_run_pipeline_2.png?fit=700%2C149&ssl=1" alt="" class="wp-image-6206 lazy" data-srcset="https://via-internet.de/blog/wp-content/uploads/2020/03/22_run_pipeline_2.png 2368w, https://via-internet.de/blog/wp-content/uploads/2020/03/22_run_pipeline_2-300x64.png 300w, https://via-internet.de/blog/wp-content/uploads/2020/03/22_run_pipeline_2-1024x218.png 1024w, https://via-internet.de/blog/wp-content/uploads/2020/03/22_run_pipeline_2-768x163.png 768w, https://via-internet.de/blog/wp-content/uploads/2020/03/22_run_pipeline_2-1536x327.png 1536w, https://via-internet.de/blog/wp-content/uploads/2020/03/22_run_pipeline_2-2048x436.png 2048w" data-sizes="auto, (max-width: 2368px) 100vw, 2368px"></figure><p>Now, click on the pipeline (e.g. the text <code>master</code> or the status icon) and you will see the pipeline with all steps and their corresponding state.</p><figure class="wp-block-image alignwide size-large"><img decoding="async" width="2364" height="1080" src="data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%202364%201080'%3E%3C/svg%3E" data-src="https://i0.wp.com/blog.via-internet.de/wp-content/uploads/2020/03/23_view_pipeline-1.png?fit=700%2C320&ssl=1" alt="" class="wp-image-6208 lazy" data-srcset="https://via-internet.de/blog/wp-content/uploads/2020/03/23_view_pipeline-1.png 2364w, https://via-internet.de/blog/wp-content/uploads/2020/03/23_view_pipeline-1-300x137.png 300w, https://via-internet.de/blog/wp-content/uploads/2020/03/23_view_pipeline-1-1024x468.png 1024w, https://via-internet.de/blog/wp-content/uploads/2020/03/23_view_pipeline-1-768x351.png 768w, https://via-internet.de/blog/wp-content/uploads/2020/03/23_view_pipeline-1-1536x702.png 1536w, https://via-internet.de/blog/wp-content/uploads/2020/03/23_view_pipeline-1-2048x936.png 2048w, https://via-internet.de/blog/wp-content/uploads/2020/03/23_view_pipeline-1-517x235.png 517w" data-sizes="auto, (max-width: 2364px) 100vw, 2364px"></figure><p>If you, want to edit the pipeline, for example to add another step, like on the pencil in the header.</p><figure class="wp-block-image alignwide size-large"><img decoding="async" src="data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%201%201'%3E%3C/svg%3E" data-src="https://blog.via-internet.de/wp-content/uploads/2020/03/23_view_pipeline_edit_buttomX-700x31.png" alt="" class="wp-image-6242 lazy"></figure><figure class="wp-block-image alignwide size-large"><img decoding="async" width="2378" height="632" src="data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%202378%20632'%3E%3C/svg%3E" data-src="https://i2.wp.com/blog.via-internet.de/wp-content/uploads/2020/03/24_edit_pipeline-1.png?fit=700%2C186&ssl=1" alt="" class="wp-image-6211 lazy" data-srcset="https://via-internet.de/blog/wp-content/uploads/2020/03/24_edit_pipeline-1.png 2378w, https://via-internet.de/blog/wp-content/uploads/2020/03/24_edit_pipeline-1-300x80.png 300w, https://via-internet.de/blog/wp-content/uploads/2020/03/24_edit_pipeline-1-1024x272.png 1024w, https://via-internet.de/blog/wp-content/uploads/2020/03/24_edit_pipeline-1-768x204.png 768w, https://via-internet.de/blog/wp-content/uploads/2020/03/24_edit_pipeline-1-1536x408.png 1536w, https://via-internet.de/blog/wp-content/uploads/2020/03/24_edit_pipeline-1-2048x544.png 2048w" data-sizes="auto, (max-width: 2378px) 100vw, 2378px"></figure><p>Click on <code>Cancel</code> to leave the Pipeline editor.</p><p><strong>Hint</strong>: If you click on <code>Save</code>, all changes are pushed back to the repository and Jenkins starts the Pipeline again.</p><h2 class="wp-block-heading">Run the Pipeline</h2><p>If you want to run your pipeline, click on the rerun icon for your pipeline</p><figure class="wp-block-image size-large"><img decoding="async" width="2341" height="138" src="data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%202341%20138'%3E%3C/svg%3E" data-src="https://i1.wp.com/blog.via-internet.de/wp-content/uploads/2020/03/31_run_pipeline_rerun.png?fit=700%2C41&ssl=1" alt="" class="wp-image-6212 lazy" data-srcset="https://via-internet.de/blog/wp-content/uploads/2020/03/31_run_pipeline_rerun.png 2341w, https://via-internet.de/blog/wp-content/uploads/2020/03/31_run_pipeline_rerun-300x18.png 300w, https://via-internet.de/blog/wp-content/uploads/2020/03/31_run_pipeline_rerun-1024x60.png 1024w, https://via-internet.de/blog/wp-content/uploads/2020/03/31_run_pipeline_rerun-768x45.png 768w, https://via-internet.de/blog/wp-content/uploads/2020/03/31_run_pipeline_rerun-1536x91.png 1536w, https://via-internet.de/blog/wp-content/uploads/2020/03/31_run_pipeline_rerun-2048x121.png 2048w" data-sizes="auto, (max-width: 2341px) 100vw, 2341px"></figure>
gitclonehttps://github.com/jenkins−toolbox/SampleAppGroovyCalculatorCloninginto′SampleAppGroovyCalculator′...remote:Enumeratingobjects:194,done.remote:Countingobjects:100remote:Compressingobjects:100remote:Total194(delta44),reused137(delta23),pack−reused0Receivingobjects:100Resolvingdeltas:100<h4class="wp−block−heading">Gointothenewcreatefolder</h4><preclass="EnlighterJSRAW"data−enlighter−language="shell"data−enlighter−theme=""data−enlighter−highlight=""data−enlighter−linenumbers=""data−enlighter−lineoffset=""data−enlighter−title=""data−enlighter−group=""> cd SampleApp_GroovyCalculator/
lsJenkinsfileREADME.mdbinbuild.gradlegradlewsrcMakefileSampleCalculatorbuildgradlesettings.gradle</pre><h4class="wp−block−heading">Thefirsttask,Jenkinswilldoinourpipeline:buildyourapp</h4><preclass="EnlighterJSRAW"data−enlighter−language="generic"data−enlighter−theme=""data−enlighter−highlight=""data−enlighter−linenumbers=""data−enlighter−lineoffset=""data−enlighter−title=""data−enlighter−group=""> ./gradlew build</pre><p>Because it’s the first time you start <code>gradlew</code>, the required software will be downloaded:</p><p>First: the current Gradle Version (Gradle is the Build Tool used by Groovy Projects)</p><pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">Downloading https://services.gradle.org/distributions/gradle-6.2.1-bin.zip
………10
Welcome to Gradle 6.2.1!
Here are the highlights of this release:
- Dependency checksum and signature verification
- Shareable read-only dependency cache
- Documentation links in deprecation messages
For more details see https://docs.gradle.org/6.2.1/release-notes.html
Starting a Gradle Daemon, 2 stopped Daemons could not be reused, use --status for details</pre><h4 class="wp-block-heading">After this, your app will be tested</h4><pre class="EnlighterJSRAW" data-enlighter-language="ini" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">> Task :test
Calculator02Spec > two plus two should equal four PASSED
Calculator01Spec > add: 2 + 3 PASSED
Calculator01Spec > subtract: 4 - 3 PASSED
Calculator01Spec > multiply: 2 * 3 PASSED
BUILD SUCCESSFUL in 34s
5 actionable tasks: 5 executed</pre><h4 class="wp-block-heading">Perform the build again</h4><p>No download is required. The build is much quicker.</p><pre class="EnlighterJSRAW" data-enlighter-language="shell" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">$ ./gradlew build
BUILD SUCCESSFUL in 1s
5 actionable tasks: 5 up-to-date</pre><h4 class="wp-block-heading">Now, test our app:</h4><pre class="EnlighterJSRAW" data-enlighter-language="shell" data-enlighter-theme="" data-enlighter-highlight="1" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">./gradlew clean test
> Task :test
Calculator02Spec > two plus two should equal four PASSED
Calculator01Spec > add: 2 + 3 PASSED
Calculator01Spec > subtract: 4 - 3 PASSED
Calculator01Spec > multiply: 2 * 3 PASSED
BUILD SUCCESSFUL in 4s
5 actionable tasks: 5 executed</pre><h2 class="wp-block-heading">Create a Jenkins Pipeline</h2><p>Start by clicking on the BlueOcean menu item.</p><div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-1 wp-block-columns-is-layout-flex"><div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:33.33%"><figure class="wp-block-image size-large"><img decoding="async" width="500" height="775" src="data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20500%20775'%3E%3C/svg%3E" data-src="https://blog.via-internet.de/wp-content/uploads/2020/03/10_blueocean_menuitem-1.png" alt="" class="wp-image-6230 lazy" data-srcset="https://via-internet.de/blog/wp-content/uploads/2020/03/10_blueocean_menuitem-1.png 500w, https://via-internet.de/blog/wp-content/uploads/2020/03/10_blueocean_menuitem-1-194x300.png 194w" data-sizes="auto, (max-width: 500px) 100vw, 500px"></figure></div><div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:66.66%"><p><strong>Hint:</strong> Blue Ocean is not installed with the default Jenkins installation.</p><p>You have to install the corresponding Plugins.</p><p>Select <code>Manage Jenkins</code> → <code>Manage Plugins</code>.</p><p>Then, select the tab <code>Available</code> and enter in the <code>Filter</code> box: Blue Ocean.</p><p>Install all plugins, that will be listed.</p></div></div><p>Next: Click on the New Pipeline to create your first Pipeline</p><figure class="wp-block-image alignwide size-large"><img decoding="async" width="2040" height="266" src="data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%202040%20266'%3E%3C/svg%3E" data-src="https://i2.wp.com/blog.via-internet.de/wp-content/uploads/2020/03/11_create_pipeline_01-1.png?fit=700%2C91&ssl=1" alt="" class="wp-image-6233 lazy" data-srcset="https://via-internet.de/blog/wp-content/uploads/2020/03/11_create_pipeline_01-1.png 2040w, https://via-internet.de/blog/wp-content/uploads/2020/03/11_create_pipeline_01-1-300x39.png 300w, https://via-internet.de/blog/wp-content/uploads/2020/03/11_create_pipeline_01-1-1024x134.png 1024w, https://via-internet.de/blog/wp-content/uploads/2020/03/11_create_pipeline_01-1-768x100.png 768w, https://via-internet.de/blog/wp-content/uploads/2020/03/11_create_pipeline_01-1-1536x200.png 1536w" data-sizes="auto, (max-width: 2040px) 100vw, 2040px"></figure><p>Use the Item GitHub to specify, where our code is stored</p><figure class="wp-block-image alignwide size-large"><img decoding="async" width="2038" height="908" src="data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%202038%20908'%3E%3C/svg%3E" data-src="https://i2.wp.com/blog.via-internet.de/wp-content/uploads/2020/03/11_create_pipeline_02-1.png?fit=700%2C312&ssl=1" alt="" class="wp-image-6234 lazy" data-srcset="https://via-internet.de/blog/wp-content/uploads/2020/03/11_create_pipeline_02-1.png 2038w, https://via-internet.de/blog/wp-content/uploads/2020/03/11_create_pipeline_02-1-300x134.png 300w, https://via-internet.de/blog/wp-content/uploads/2020/03/11_create_pipeline_02-1-1024x456.png 1024w, https://via-internet.de/blog/wp-content/uploads/2020/03/11_create_pipeline_02-1-768x342.png 768w, https://via-internet.de/blog/wp-content/uploads/2020/03/11_create_pipeline_02-1-1536x684.png 1536w" data-sizes="auto, (max-width: 2038px) 100vw, 2038px"></figure><p>Next, use your GitHub account.</p><p>Be sure, that you cloned the <a href="https://github.com/jenkins-toolbox/SampleApp_GroovyCalculator">demo repository</a></p><figure class="wp-block-image alignwide size-large"><img decoding="async" src="data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%201%201'%3E%3C/svg%3E" data-src="https://blog.via-internet.de/wp-content/uploads/2020/03/11_create_pipeline_03-700x450.png" alt="" class="wp-image-6200 lazy"></figure><p>Next, we select the demo repository <a href="https://github.com/jenkins-toolbox/SampleApp_GroovyCalculator">SampleApp_GroovyCalculator</a></p><figure class="wp-block-image alignwide size-large"><img decoding="async" src="data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%201%201'%3E%3C/svg%3E" data-src="https://blog.via-internet.de/wp-content/uploads/2020/03/11_create_pipeline_04-700x708.png" alt="" class="wp-image-6201 lazy"></figure><p>Click on Create Pipeline and after a few seconds, the pipeline is created.</p><figure class="wp-block-image alignwide size-large"><img decoding="async" src="data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%201%201'%3E%3C/svg%3E" data-src="https://blog.via-internet.de/wp-content/uploads/2020/03/11_create_pipeline_06-700x732.png" alt="" class="wp-image-6203 lazy"></figure><p>Immediately after creating the pipeline, Jenkins is starting the pipeline and all steps included.</p><figure class="wp-block-image alignwide size-large"><img decoding="async" width="2371" height="524" src="data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%202371%20524'%3E%3C/svg%3E" data-src="https://i0.wp.com/blog.via-internet.de/wp-content/uploads/2020/03/22_run_pipeline_1.png?fit=700%2C155&ssl=1" alt="" class="wp-image-6205 lazy" data-srcset="https://via-internet.de/blog/wp-content/uploads/2020/03/22_run_pipeline_1.png 2371w, https://via-internet.de/blog/wp-content/uploads/2020/03/22_run_pipeline_1-300x66.png 300w, https://via-internet.de/blog/wp-content/uploads/2020/03/22_run_pipeline_1-1024x226.png 1024w, https://via-internet.de/blog/wp-content/uploads/2020/03/22_run_pipeline_1-768x170.png 768w, https://via-internet.de/blog/wp-content/uploads/2020/03/22_run_pipeline_1-1536x339.png 1536w, https://via-internet.de/blog/wp-content/uploads/2020/03/22_run_pipeline_1-2048x453.png 2048w" data-sizes="auto, (max-width: 2371px) 100vw, 2371px"></figure><p>If everything went well, you see a positive status</p><figure class="wp-block-image alignwide size-large"><img decoding="async" width="2368" height="504" src="data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%202368%20504'%3E%3C/svg%3E" data-src="https://i1.wp.com/blog.via-internet.de/wp-content/uploads/2020/03/22_run_pipeline_2.png?fit=700%2C149&ssl=1" alt="" class="wp-image-6206 lazy" data-srcset="https://via-internet.de/blog/wp-content/uploads/2020/03/22_run_pipeline_2.png 2368w, https://via-internet.de/blog/wp-content/uploads/2020/03/22_run_pipeline_2-300x64.png 300w, https://via-internet.de/blog/wp-content/uploads/2020/03/22_run_pipeline_2-1024x218.png 1024w, https://via-internet.de/blog/wp-content/uploads/2020/03/22_run_pipeline_2-768x163.png 768w, https://via-internet.de/blog/wp-content/uploads/2020/03/22_run_pipeline_2-1536x327.png 1536w, https://via-internet.de/blog/wp-content/uploads/2020/03/22_run_pipeline_2-2048x436.png 2048w" data-sizes="auto, (max-width: 2368px) 100vw, 2368px"></figure><p>Now, click on the pipeline (e.g. the text <code>master</code> or the status icon) and you will see the pipeline with all steps and their corresponding state.</p><figure class="wp-block-image alignwide size-large"><img decoding="async" width="2364" height="1080" src="data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%202364%201080'%3E%3C/svg%3E" data-src="https://i0.wp.com/blog.via-internet.de/wp-content/uploads/2020/03/23_view_pipeline-1.png?fit=700%2C320&ssl=1" alt="" class="wp-image-6208 lazy" data-srcset="https://via-internet.de/blog/wp-content/uploads/2020/03/23_view_pipeline-1.png 2364w, https://via-internet.de/blog/wp-content/uploads/2020/03/23_view_pipeline-1-300x137.png 300w, https://via-internet.de/blog/wp-content/uploads/2020/03/23_view_pipeline-1-1024x468.png 1024w, https://via-internet.de/blog/wp-content/uploads/2020/03/23_view_pipeline-1-768x351.png 768w, https://via-internet.de/blog/wp-content/uploads/2020/03/23_view_pipeline-1-1536x702.png 1536w, https://via-internet.de/blog/wp-content/uploads/2020/03/23_view_pipeline-1-2048x936.png 2048w, https://via-internet.de/blog/wp-content/uploads/2020/03/23_view_pipeline-1-517x235.png 517w" data-sizes="auto, (max-width: 2364px) 100vw, 2364px"></figure><p>If you, want to edit the pipeline, for example to add another step, like on the pencil in the header.</p><figure class="wp-block-image alignwide size-large"><img decoding="async" src="data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%201%201'%3E%3C/svg%3E" data-src="https://blog.via-internet.de/wp-content/uploads/2020/03/23_view_pipeline_edit_buttomX-700x31.png" alt="" class="wp-image-6242 lazy"></figure><figure class="wp-block-image alignwide size-large"><img decoding="async" width="2378" height="632" src="data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%202378%20632'%3E%3C/svg%3E" data-src="https://i2.wp.com/blog.via-internet.de/wp-content/uploads/2020/03/24_edit_pipeline-1.png?fit=700%2C186&ssl=1" alt="" class="wp-image-6211 lazy" data-srcset="https://via-internet.de/blog/wp-content/uploads/2020/03/24_edit_pipeline-1.png 2378w, https://via-internet.de/blog/wp-content/uploads/2020/03/24_edit_pipeline-1-300x80.png 300w, https://via-internet.de/blog/wp-content/uploads/2020/03/24_edit_pipeline-1-1024x272.png 1024w, https://via-internet.de/blog/wp-content/uploads/2020/03/24_edit_pipeline-1-768x204.png 768w, https://via-internet.de/blog/wp-content/uploads/2020/03/24_edit_pipeline-1-1536x408.png 1536w, https://via-internet.de/blog/wp-content/uploads/2020/03/24_edit_pipeline-1-2048x544.png 2048w" data-sizes="auto, (max-width: 2378px) 100vw, 2378px"></figure><p>Click on <code>Cancel</code> to leave the Pipeline editor.</p><p><strong>Hint</strong>: If you click on <code>Save</code>, all changes are pushed back to the repository and Jenkins starts the Pipeline again.</p><h2 class="wp-block-heading">Run the Pipeline</h2><p>If you want to run your pipeline, click on the rerun icon for your pipeline</p><figure class="wp-block-image size-large"><img decoding="async" width="2341" height="138" src="data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%202341%20138'%3E%3C/svg%3E" data-src="https://i1.wp.com/blog.via-internet.de/wp-content/uploads/2020/03/31_run_pipeline_rerun.png?fit=700%2C41&ssl=1" alt="" class="wp-image-6212 lazy" data-srcset="https://via-internet.de/blog/wp-content/uploads/2020/03/31_run_pipeline_rerun.png 2341w, https://via-internet.de/blog/wp-content/uploads/2020/03/31_run_pipeline_rerun-300x18.png 300w, https://via-internet.de/blog/wp-content/uploads/2020/03/31_run_pipeline_rerun-1024x60.png 1024w, https://via-internet.de/blog/wp-content/uploads/2020/03/31_run_pipeline_rerun-768x45.png 768w, https://via-internet.de/blog/wp-content/uploads/2020/03/31_run_pipeline_rerun-1536x91.png 1536w, https://via-internet.de/blog/wp-content/uploads/2020/03/31_run_pipeline_rerun-2048x121.png 2048w" data-sizes="auto, (max-width: 2341px) 100vw, 2341px"></figure>
For more details see https://docs.gradle.org/6.2.1/release-notes.html
Starting a Gradle Daemon, 2 stopped Daemons could not be reused, use --status for details
Downloading https://services.gradle.org/distributions/gradle-6.2.1-bin.zip
………10
Welcome to Gradle 6.2.1!
Here are the highlights of this release:
- Dependency checksum and signature verification
- Shareable read-only dependency cache
- Documentation links in deprecation messages
For more details see https://docs.gradle.org/6.2.1/release-notes.html
Starting a Gradle Daemon, 2 stopped Daemons could not be reused, use --status for details
Downloading https://services.gradle.org/distributions/gradle-6.2.1-bin.zip
………10
Welcome to Gradle 6.2.1!
Here are the highlights of this release:
- Dependency checksum and signature verification
- Shareable read-only dependency cache
- Documentation links in deprecation messages
For more details see https://docs.gradle.org/6.2.1/release-notes.html
Starting a Gradle Daemon, 2 stopped Daemons could not be reused, use --status for details
After this, your app will be tested
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
> Task :test
Calculator02Spec > two plus two should equal four PASSED
Calculator01Spec > add: 2 + 3 PASSED
Calculator01Spec > subtract: 4 - 3 PASSED
Calculator01Spec > multiply: 2 * 3 PASSED
BUILD SUCCESSFUL in 34s
5 actionable tasks: 5 executed
> Task :test
Calculator02Spec > two plus two should equal four PASSED
Calculator01Spec > add: 2 + 3 PASSED
Calculator01Spec > subtract: 4 - 3 PASSED
Calculator01Spec > multiply: 2 * 3 PASSED
BUILD SUCCESSFUL in 34s
5 actionable tasks: 5 executed
> Task :test
Calculator02Spec > two plus two should equal four PASSED
Calculator01Spec > add: 2 + 3 PASSED
Calculator01Spec > subtract: 4 - 3 PASSED
Calculator01Spec > multiply: 2 * 3 PASSED
BUILD SUCCESSFUL in 34s
5 actionable tasks: 5 executed
Perform the build again
No download is required. The build is much quicker.
$ az webapp up --sku F1 -n azure-toolbox-flask-demo -l westeurope
webapp azure-toolbox-flask-demo doesn't exist
Creating Resource group 'xx_xx_Linux_westeurope' ...
Resource group creation complete
Creating AppServicePlan 'xx_asp_Linux_westeurope_0' ...
Creating webapp 'flask-demo' ...
Configuring default logging for the app, if not already enabled
Creating zip with contents of dir .../Working-with_Python ...
Getting scm site credentials for zip deployment
Starting zip deployment. This operation can take a while to complete ...
Deployment endpoint responded with status code 202
You can launch the app at http://via-internet-flask-demo.azurewebsites.net
{
"URL": "http:/azure-toolbox-flask-demo.azurewebsites.net",
"appserviceplan": "xx_asp_Linux_westeurope_0",
"location": "westeurope",
"name": "azure-toolbox--flask-demo",
"os": "Linux",
"resourcegroup": "xx_xx_Linux_westeurope",
"runtime_version": "python|3.7",
"runtime_version_detected": "-",
"sku": "FREE",
"src_path": ".../Working-with_Python"
}
$ az webapp up --sku F1 -n azure-toolbox-flask-demo -l westeurope
webapp azure-toolbox-flask-demo doesn't exist
Creating Resource group 'xx_xx_Linux_westeurope' ...
Resource group creation complete
Creating AppServicePlan 'xx_asp_Linux_westeurope_0' ...
Creating webapp 'flask-demo' ...
Configuring default logging for the app, if not already enabled
Creating zip with contents of dir .../Working-with_Python ...
Getting scm site credentials for zip deployment
Starting zip deployment. This operation can take a while to complete ...
Deployment endpoint responded with status code 202
You can launch the app at http://via-internet-flask-demo.azurewebsites.net
{
"URL": "http:/azure-toolbox-flask-demo.azurewebsites.net",
"appserviceplan": "xx_asp_Linux_westeurope_0",
"location": "westeurope",
"name": "azure-toolbox--flask-demo",
"os": "Linux",
"resourcegroup": "xx_xx_Linux_westeurope",
"runtime_version": "python|3.7",
"runtime_version_detected": "-",
"sku": "FREE",
"src_path": ".../Working-with_Python"
}
Create Django App with PostgreSQL
Installation PostgreSQL on Mac OS
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
$ brew install postgres
==> Installing dependencies for postgresql: krb5
==> Installing postgresql dependency: krb5
...
==> Installing postgresql
...
==> Caveats
==> krb5
krb5 is keg-only, which means it was not symlinked into /usr/local, because macOS already provides this software and installing another version in
To migrate existing data from a previous major version of PostgreSQL run:
brew postgresql-upgrade-database
To have launchd start postgresql now and restart at login:
brew services start postgresql
Or, if you don't want/need a background service you can just run:
pg_ctl -D /usr/local/var/postgres start
brewinstallpostgres==>Installingdependenciesforpostgresql:krb5==>Installingpostgresqldependency:krb5...==>Installingpostgresql...==>Caveats==>krb5krb5iskeg−only,whichmeansitwasnotsymlinkedinto/usr/local,becausemacOSalreadyprovidesthissoftwareandinstallinganotherversioninparallelcancauseallkindsoftrouble.Ifyouneedtohavekrb5firstinyourPATHrun:echo′exportPATH="/usr/local/opt/krb5/bin:PATH"' >> ~/.bash_profile
echo 'export PATH="/usr/local/opt/krb5/sbin:$PATH"' >> ~/.bash_profile
For compilers to find krb5 you may need to set:
export LDFLAGS="-L/usr/local/opt/krb5/lib"
export CPPFLAGS="-I/usr/local/opt/krb5/include"
For pkg-config to find krb5 you may need to set:
export PKG_CONFIG_PATH="/usr/local/opt/krb5/lib/pkgconfig"
==> postgresql
To migrate existing data from a previous major version of PostgreSQL run:
brew postgresql-upgrade-database
To have launchd start postgresql now and restart at login:
brew services start postgresql
Or, if you don't want/need a background service you can just run:
pg_ctl -D /usr/local/var/postgres start
$ brew install postgres
==> Installing dependencies for postgresql: krb5
==> Installing postgresql dependency: krb5
...
==> Installing postgresql
...
==> Caveats
==> krb5
krb5 is keg-only, which means it was not symlinked into /usr/local, because macOS already provides this software and installing another version in
parallel can cause all kinds of trouble.
If you need to have krb5 first in your PATH run:
echo 'export PATH="/usr/local/opt/krb5/bin:$PATH"' >> ~/.bash_profile
echo 'export PATH="/usr/local/opt/krb5/sbin:$PATH"' >> ~/.bash_profile
For compilers to find krb5 you may need to set:
export LDFLAGS="-L/usr/local/opt/krb5/lib"
export CPPFLAGS="-I/usr/local/opt/krb5/include"
For pkg-config to find krb5 you may need to set:
export PKG_CONFIG_PATH="/usr/local/opt/krb5/lib/pkgconfig"
==> postgresql
To migrate existing data from a previous major version of PostgreSQL run:
brew postgresql-upgrade-database
To have launchd start postgresql now and restart at login:
brew services start postgresql
Or, if you don't want/need a background service you can just run:
pg_ctl -D /usr/local/var/postgres start
Set user and passwords for postgres database
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
Create database and user for django app
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
$ psql postgres
psql (12.1)
Type "help"for help.
postgres=# CREATE DATABASE pollsdb;
CREATE DATABASE
postgres=# CREATE USER manager WITH PASSWORD '########';
CREATE ROLE
postgres=# GRANT ALL PRIVILEGES ON DATABASE pollsdb TO manager;
GRANT
$ psql postgres
psql (12.1)
Type "help" for help.
postgres=# CREATE DATABASE pollsdb;
CREATE DATABASE
postgres=# CREATE USER manager WITH PASSWORD '########';
CREATE ROLE
postgres=# GRANT ALL PRIVILEGES ON DATABASE pollsdb TO manager;
GRANT
$ psql postgres
psql (12.1)
Type "help" for help.
postgres=# CREATE DATABASE pollsdb;
CREATE DATABASE
postgres=# CREATE USER manager WITH PASSWORD '########';
CREATE ROLE
postgres=# GRANT ALL PRIVILEGES ON DATABASE pollsdb TO manager;
GRANT
Apply all migrations: admin, auth, contenttypes, polls, sessions
Running migrations:
Applying contenttypes.0001_initial... OK
Applying auth.0001_initial... OK
Applying admin.0001_initial... OK
Applying admin.0002_logentry_remove_auto_add... OK
Applying admin.0003_logentry_add_action_flag_choices... OK
Applying contenttypes.0002_remove_content_type_name... OK
Applying auth.0002_alter_permission_name_max_length... OK
Applying auth.0003_alter_user_email_max_length... OK
Applying auth.0004_alter_user_username_opts... OK
Applying auth.0005_alter_user_last_login_null... OK
Applying auth.0006_require_contenttypes_0002... OK
Applying auth.0007_alter_validators_add_error_messages... OK
Applying auth.0008_alter_user_username_max_length... OK
Applying auth.0009_alter_user_last_name_max_length... OK
Applying polls.0001_initial... OK
Applying sessions.0001_initial... OK
pythonmanage.pymakemigrationsNochangesdetected python manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, contenttypes, polls, sessions
Running migrations:
Applying contenttypes.0001_initial... OK
Applying auth.0001_initial... OK
Applying admin.0001_initial... OK
Applying admin.0002_logentry_remove_auto_add... OK
Applying admin.0003_logentry_add_action_flag_choices... OK
Applying contenttypes.0002_remove_content_type_name... OK
Applying auth.0002_alter_permission_name_max_length... OK
Applying auth.0003_alter_user_email_max_length... OK
Applying auth.0004_alter_user_username_opts... OK
Applying auth.0005_alter_user_last_login_null... OK
Applying auth.0006_require_contenttypes_0002... OK
Applying auth.0007_alter_validators_add_error_messages... OK
Applying auth.0008_alter_user_username_max_length... OK
Applying auth.0009_alter_user_last_name_max_length... OK
Applying polls.0001_initial... OK
Applying sessions.0001_initial... OK
$ python manage.py makemigrations
No changes detected
$ python manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, contenttypes, polls, sessions
Running migrations:
Applying contenttypes.0001_initial... OK
Applying auth.0001_initial... OK
Applying admin.0001_initial... OK
Applying admin.0002_logentry_remove_auto_add... OK
Applying admin.0003_logentry_add_action_flag_choices... OK
Applying contenttypes.0002_remove_content_type_name... OK
Applying auth.0002_alter_permission_name_max_length... OK
Applying auth.0003_alter_user_email_max_length... OK
Applying auth.0004_alter_user_username_opts... OK
Applying auth.0005_alter_user_last_login_null... OK
Applying auth.0006_require_contenttypes_0002... OK
Applying auth.0007_alter_validators_add_error_messages... OK
Applying auth.0008_alter_user_username_max_length... OK
Applying auth.0009_alter_user_last_name_max_length... OK
Applying polls.0001_initial... OK
Applying sessions.0001_initial... OK
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
$ python manage.py createsuperuser
Username(leave blank to use 'user'): admin
Email address: admin@localhost
Password:
Password(again):
Superuser created successfully.
$ python manage.py createsuperuser
Username (leave blank to use 'user'): admin
Email address: admin@localhost
Password:
Password (again):
Superuser created successfully.
$ python manage.py createsuperuser
Username (leave blank to use 'user'): admin
Email address: admin@localhost
Password:
Password (again):
Superuser created successfully.
Run server and acccess web page at http://127.0.0.1:8000/
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
$ python manage.py runserver
Performing system checks...
System check identified no issues (0 silenced).
January 25, 2020 - 16:42:14
Django version 2.1.2, using settings 'azuresite.settings'
Starting development server at http://127.0.0.1:8000/
$ python manage.py runserver
Performing system checks...
System check identified no issues (0 silenced).
January 25, 2020 - 16:42:14
Django version 2.1.2, using settings 'azuresite.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
[25/Jan/2020 16:42:26] "GET / HTTP/1.1" 200 111
[25/Jan/2020 16:42:26] "GET /static/polls/style.css HTTP/1.1" 200 27
Not Found: /favicon.ico
[25/Jan/2020 16:42:26] "GET /favicon.ico HTTP/1.1" 404 2688
$ python manage.py runserver
Performing system checks...
System check identified no issues (0 silenced).
January 25, 2020 - 16:42:14
Django version 2.1.2, using settings 'azuresite.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
[25/Jan/2020 16:42:26] "GET / HTTP/1.1" 200 111
[25/Jan/2020 16:42:26] "GET /static/polls/style.css HTTP/1.1" 200 27
Not Found: /favicon.ico
[25/Jan/2020 16:42:26] "GET /favicon.ico HTTP/1.1" 404 2688
Login zu Azure
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
$ az login
$ az login
$ az login
Deploy to App Service
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
$ az webapp up --sku F1 -n azure-toolbox-django-demo -l westeurope
webapp azure-toolbox-django-demo doesn't exist
Creating Resource group 'xx_xx_Linux_westeurope' ...
$ az webapp up --sku F1 -n azure-toolbox-django-demo -l westeurope
webapp azure-toolbox-django-demo doesn't exist
Creating Resource group 'xx_xx_Linux_westeurope' ...
Resource group creation complete
Creating AppServicePlan 'xx_asp_Linux_westeurope_0' ...
Creating webapp 'flask-demo' ...
Configuring default logging for the app, if not already enabled
Creating zip with contents of dir .../Working-with_Django ...
Getting scm site credentials for zip deployment
Starting zip deployment. This operation can take a while to complete ...
Deployment endpoint responded with status code 202
You can launch the app at http://via-internet-django-demo.azurewebsites.net
{
"URL": "http:/azure-toolbox-django-demo.azurewebsites.net",
"appserviceplan": "xx_asp_Linux_westeurope_0",
"location": "westeurope",
"name": "azure-toolbox--django-demo",
"os": "Linux",
"resourcegroup": "xx_xx_Linux_westeurope",
"runtime_version": "python|3.7",
"runtime_version_detected": "-",
"sku": "FREE",
"src_path": ".../Working-with_Django"
}
$ az webapp up --sku F1 -n azure-toolbox-django-demo -l westeurope
webapp azure-toolbox-django-demo doesn't exist
Creating Resource group 'xx_xx_Linux_westeurope' ...
Resource group creation complete
Creating AppServicePlan 'xx_asp_Linux_westeurope_0' ...
Creating webapp 'flask-demo' ...
Configuring default logging for the app, if not already enabled
Creating zip with contents of dir .../Working-with_Django ...
Getting scm site credentials for zip deployment
Starting zip deployment. This operation can take a while to complete ...
Deployment endpoint responded with status code 202
You can launch the app at http://via-internet-django-demo.azurewebsites.net
{
"URL": "http:/azure-toolbox-django-demo.azurewebsites.net",
"appserviceplan": "xx_asp_Linux_westeurope_0",
"location": "westeurope",
"name": "azure-toolbox--django-demo",
"os": "Linux",
"resourcegroup": "xx_xx_Linux_westeurope",
"runtime_version": "python|3.7",
"runtime_version_detected": "-",
"sku": "FREE",
"src_path": ".../Working-with_Django"
}
.share(); // remove this line: console will log every result 3 times instead of 1
squareData:Observable<string>=Observable.range(0,10).map(x=>x∗x).do(x=>console.log(‘CalculationResult:{x}`)
.toArray()
.map(squares => squares.join(", "))
.share(); // remove this line: console will log every result 3 times instead of 1
squareData$: Observable<string> = Observable.range(0, 10)
.map(x => x * x)
.do(x => console.log(`CalculationResult: ${x}`)
.toArray()
.map(squares => squares.join(", "))
.share(); // remove this line: console will log every result 3 times instead of 1
Managing Cold and Hot Observables using publish().refCount() which is similar to .share()
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
ngOnInit(){
// in angular 2 and above component.ts file add these this.coldObservable();
this.hotObservable();
}
/*
* cold observable is like a recast of video
* */
coldObservable(){
let incrementalObs = Observable.interval(1000).take(10).map(x => x + 1);
ngOnInit() {
// in angular 2 and above component.ts file add these this.coldObservable();
this.hotObservable();
}
/*
* cold observable is like a recast of video
* */
coldObservable() {
let incrementalObs = Observable.interval(1000).take(10).map(x => x + 1);
incrementalObs.subscribe(val => console.log('a: ' + val));
setTimeout(function() {
incrementalObs.subscribe(val => console.log(' b: ' + val));
}, 4500);
}
/*
* hot observable is like watching a live video
* */
hotObservable() {
let incrementalObs = Observable.interval(1000).take(10).map(x => x + 1).publish().refCount(); //can also use .share()
incrementalObs.subscribe(val => console.log('a: ' + val));
setTimeout(function() {
incrementalObs.subscribe(val => console.log(' b: ' + val));
}, 4500);
}
ngOnInit() {
// in angular 2 and above component.ts file add these this.coldObservable();
this.hotObservable();
}
/*
* cold observable is like a recast of video
* */
coldObservable() {
let incrementalObs = Observable.interval(1000).take(10).map(x => x + 1);
incrementalObs.subscribe(val => console.log('a: ' + val));
setTimeout(function() {
incrementalObs.subscribe(val => console.log(' b: ' + val));
}, 4500);
}
/*
* hot observable is like watching a live video
* */
hotObservable() {
let incrementalObs = Observable.interval(1000).take(10).map(x => x + 1).publish().refCount(); //can also use .share()
incrementalObs.subscribe(val => console.log('a: ' + val));
setTimeout(function() {
incrementalObs.subscribe(val => console.log(' b: ' + val));
}, 4500);
}
* This uses Catch for V1. This introduces Closure. It is effectively the same as V2.
* */
howToHandleErrorV1(){
this.obs
.map(x =>{
if( x === 3){
throw 'I hate threes'; // When it hitted error it actually unsubscribe itself at x === 3 of throw error
}
return x;
})
.catch(err => Observable.throw('Caught error here Observable.throw'))// continue go down the error path use Observable.throw
.catch(err => Observable.of('Caught error here Observable.of'))// catch just use Observable.of
.subscribe(
x => console.log(x),
err => console.error(err), // If not catch any where, the I hate threes errors will be propagated to here
() => console.log('done completed')
);
}
/*
* There is a difference between V1 and V2. For V2 it is using onErrorResumeNext which
* */
howToHandleErrorV2(){
let good = Observable.of('Caught error here Observable.of');
this.obs
.map(x =>{
if( x === 3){
throw 'I hate threes'; // When it hit error it actually unsubscribe itself at x === 3 of throw error
}
return x;
})
.onErrorResumeNext(good)// To catch just use Observable.of
.subscribe(
x => console.log(x),
err => console.error(err), // If not catch any where, the I hate threes errors will be propagated to here
() => console.log('done completed')
);
}
/*
* For this we use see it retries three times then console.error(err);
* So retryWhen is for trying network connection websocket
* */
howToUseRetry(){
this.obs
.map(x =>{
if( x === 3){
throw 'I hate threes'; // When it hitted error it actually unsubscribe itself at x === 3 of throw error
}
return x;
})
.retry(3)// retry three times
.retryWhen(err => err.delay(2000).take(3))// similar but with 2 seconds delay and the error is not propagated.
.retryWhen(err => err.delay(2000).take(3).concat(Observable.throw('bad')))// this it would throw an error.
.subscribe(
x => console.log(x),
err => console.error(err), // If not catch any where, the I hate threes errors will be propagated to here
() => console.log('done completed')
);
}
/*
* Using observable merge operator
* */
mergeObservableAndThrowError(){
let mergedObs = Observable.merge(
this.obs, //1, 2, 3, 4
Observable.throw('Stop Error'),
Observable.from(this.array), //0, 1, 2, 3, 4, 5
Observable.of(999)//999,
);
mergedObs.subscribe(
val => console.log(val), //this should show 1, 2, 3, 4, Stop Error
error => console.log(error),
() => console.log("completed")
);
}
/* Using observable onErrorResumeNext just like merge operator
* */
mergeObservableAndErrorResumeNext(){
let mergedObs = Observable.onErrorResumeNext(
this.obs, //1, 2, 3, 4
Observable.throw('Stop Error'),
Observable.from(this.array), //0, 1, 2, 3, 4, 5
Observable.of(999)//999,
);
mergedObs.subscribe(
val => console.log(val), //this should show 1, 2, 3, 4, 0, 1, 2, 3, 4, 5, 999
error => console.log(error),
() => console.log("completed")
);
}
/*
* Using observable merge operator and catch
* */
mergeObservableAndErrorCatch(){
let mergedObs = Observable.merge(
this.obs, //1, 2, 3, 4
Observable.throw('Stop Error'),
Observable.from(this.array), //0, 1, 2, 3, 4, 5
Observable.of(999)//999,
).catch(e =>{
console.log(e);
return Observable.of('catch error here');
});
mergedObs.subscribe(
val => console.log(val), //this should show 1, 2, 3, 4, Stop Error, Catch Error Here
error => console.log(error),
() => console.log("completed")
);
}
}
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent implements OnInit {
title = 'app works!';
obs = Observable.of(1, 2, 3, 4);
ngOnInit() {
this.howToHandleErrorV1();
this.howToHandleErrorV2();
this.howToUseRetry();
this.mergeObservableAndThrowError();
this.mergeObservableAndErrorResumeNext();
this.mergeObservableAndErrorCatch();
}
/*
* This uses Catch for V1. This introduces Closure. It is effectively the same as V2.
* */
howToHandleErrorV1() {
this.obs
.map(x => {
if ( x === 3 ) {
throw 'I hate threes'; // When it hitted error it actually unsubscribe itself at x === 3 of throw error
}
return x;
})
.catch(err => Observable.throw('Caught error here Observable.throw')) // continue go down the error path use Observable.throw
.catch(err => Observable.of('Caught error here Observable.of')) // catch just use Observable.of
.subscribe(
x => console.log(x),
err => console.error(err), // If not catch any where, the I hate threes errors will be propagated to here
() => console.log('done completed')
);
}
/*
* There is a difference between V1 and V2. For V2 it is using onErrorResumeNext which
* */
howToHandleErrorV2() {
let good = Observable.of('Caught error here Observable.of');
this.obs
.map(x => {
if ( x === 3 ) {
throw 'I hate threes'; // When it hit error it actually unsubscribe itself at x === 3 of throw error
}
return x;
})
.onErrorResumeNext(good) // To catch just use Observable.of
.subscribe(
x => console.log(x),
err => console.error(err), // If not catch any where, the I hate threes errors will be propagated to here
() => console.log('done completed')
);
}
/*
* For this we use see it retries three times then console.error(err);
* So retryWhen is for trying network connection websocket
* */
howToUseRetry() {
this.obs
.map(x => {
if ( x === 3 ) {
throw 'I hate threes'; // When it hitted error it actually unsubscribe itself at x === 3 of throw error
}
return x;
})
.retry(3) // retry three times
.retryWhen(err => err.delay(2000).take(3)) // similar but with 2 seconds delay and the error is not propagated.
.retryWhen(err => err.delay(2000).take(3).concat(Observable.throw('bad'))) // this it would throw an error.
.subscribe(
x => console.log(x),
err => console.error(err), // If not catch any where, the I hate threes errors will be propagated to here
() => console.log('done completed')
);
}
/*
* Using observable merge operator
* */
mergeObservableAndThrowError() {
let mergedObs = Observable.merge(
this.obs, //1, 2, 3, 4
Observable.throw('Stop Error'),
Observable.from(this.array), //0, 1, 2, 3, 4, 5
Observable.of(999) //999,
);
mergedObs.subscribe(
val => console.log(val), //this should show 1, 2, 3, 4, Stop Error
error => console.log(error),
() => console.log("completed")
);
}
/* Using observable onErrorResumeNext just like merge operator
* */
mergeObservableAndErrorResumeNext() {
let mergedObs = Observable.onErrorResumeNext(
this.obs, //1, 2, 3, 4
Observable.throw('Stop Error'),
Observable.from(this.array), //0, 1, 2, 3, 4, 5
Observable.of(999) //999,
);
mergedObs.subscribe(
val => console.log(val), //this should show 1, 2, 3, 4, 0, 1, 2, 3, 4, 5, 999
error => console.log(error),
() => console.log("completed")
);
}
/*
* Using observable merge operator and catch
* */
mergeObservableAndErrorCatch() {
let mergedObs = Observable.merge(
this.obs, //1, 2, 3, 4
Observable.throw('Stop Error'),
Observable.from(this.array), //0, 1, 2, 3, 4, 5
Observable.of(999) //999,
).catch(e => {
console.log(e);
return Observable.of('catch error here');
});
mergedObs.subscribe(
val => console.log(val), //this should show 1, 2, 3, 4, Stop Error, Catch Error Here
error => console.log(error),
() => console.log("completed")
);
}
}
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent implements OnInit {
title = 'app works!';
obs = Observable.of(1, 2, 3, 4);
ngOnInit() {
this.howToHandleErrorV1();
this.howToHandleErrorV2();
this.howToUseRetry();
this.mergeObservableAndThrowError();
this.mergeObservableAndErrorResumeNext();
this.mergeObservableAndErrorCatch();
}
/*
* This uses Catch for V1. This introduces Closure. It is effectively the same as V2.
* */
howToHandleErrorV1() {
this.obs
.map(x => {
if ( x === 3 ) {
throw 'I hate threes'; // When it hitted error it actually unsubscribe itself at x === 3 of throw error
}
return x;
})
.catch(err => Observable.throw('Caught error here Observable.throw')) // continue go down the error path use Observable.throw
.catch(err => Observable.of('Caught error here Observable.of')) // catch just use Observable.of
.subscribe(
x => console.log(x),
err => console.error(err), // If not catch any where, the I hate threes errors will be propagated to here
() => console.log('done completed')
);
}
/*
* There is a difference between V1 and V2. For V2 it is using onErrorResumeNext which
* */
howToHandleErrorV2() {
let good = Observable.of('Caught error here Observable.of');
this.obs
.map(x => {
if ( x === 3 ) {
throw 'I hate threes'; // When it hit error it actually unsubscribe itself at x === 3 of throw error
}
return x;
})
.onErrorResumeNext(good) // To catch just use Observable.of
.subscribe(
x => console.log(x),
err => console.error(err), // If not catch any where, the I hate threes errors will be propagated to here
() => console.log('done completed')
);
}
/*
* For this we use see it retries three times then console.error(err);
* So retryWhen is for trying network connection websocket
* */
howToUseRetry() {
this.obs
.map(x => {
if ( x === 3 ) {
throw 'I hate threes'; // When it hitted error it actually unsubscribe itself at x === 3 of throw error
}
return x;
})
.retry(3) // retry three times
.retryWhen(err => err.delay(2000).take(3)) // similar but with 2 seconds delay and the error is not propagated.
.retryWhen(err => err.delay(2000).take(3).concat(Observable.throw('bad'))) // this it would throw an error.
.subscribe(
x => console.log(x),
err => console.error(err), // If not catch any where, the I hate threes errors will be propagated to here
() => console.log('done completed')
);
}
/*
* Using observable merge operator
* */
mergeObservableAndThrowError() {
let mergedObs = Observable.merge(
this.obs, //1, 2, 3, 4
Observable.throw('Stop Error'),
Observable.from(this.array), //0, 1, 2, 3, 4, 5
Observable.of(999) //999,
);
mergedObs.subscribe(
val => console.log(val), //this should show 1, 2, 3, 4, Stop Error
error => console.log(error),
() => console.log("completed")
);
}
/* Using observable onErrorResumeNext just like merge operator
* */
mergeObservableAndErrorResumeNext() {
let mergedObs = Observable.onErrorResumeNext(
this.obs, //1, 2, 3, 4
Observable.throw('Stop Error'),
Observable.from(this.array), //0, 1, 2, 3, 4, 5
Observable.of(999) //999,
);
mergedObs.subscribe(
val => console.log(val), //this should show 1, 2, 3, 4, 0, 1, 2, 3, 4, 5, 999
error => console.log(error),
() => console.log("completed")
);
}
/*
* Using observable merge operator and catch
* */
mergeObservableAndErrorCatch() {
let mergedObs = Observable.merge(
this.obs, //1, 2, 3, 4
Observable.throw('Stop Error'),
Observable.from(this.array), //0, 1, 2, 3, 4, 5
Observable.of(999) //999,
).catch(e => {
console.log(e);
return Observable.of('catch error here');
});
mergedObs.subscribe(
val => console.log(val), //this should show 1, 2, 3, 4, Stop Error, Catch Error Here
error => console.log(error),
() => console.log("completed")
);
}
}
map vs flatMap in RxJS
Transform the items emitted by an Observable into Observables, then flatten the emissions from those into a single Observable. See my GitHub repo.
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
obs = Observable.of(1, 2, 3, 4);
ngOnInit(){
this.usingMap();
this.usingMapToMakeInnerObservable();
this.usingMapAndMergeAll();
this.usingFlatMap();
}usingMap(){
this.obs
.map(x => x *2)// transform the input by multiple of 2
.subscribe(
x => console.log(x),
err => console.error(err),
() => console.log('done completed')
);
}
usingMapToMakeInnerObservable(){
this.obs
.map(x => Observable.timer(500).map(() => x + 3))// transform the input wrapping it with another observable and addition of 3
//.map(x => Observable.timer(500).map((x) => x + 3)) // !!! REMEMBER Not the same as the immediate above
.subscribe(
x => console.log(x),
err => console.error(err),
() => console.log('done completed')
);
}
// Map and Merge all is the same as just one FlatMap
usingMapAndMergeAll(){
this.obs
.map(x => Observable.timer(500).map(() => x + 3))// transform the input wrapping it with another observable and addition of 3
.mergeAll()
.subscribe(
x => console.log(x),
err => console.error(err),
() => console.log('done completed')
);
}
// Flat map is the same as map then merge all
// transform the items emitted by an Observable into Observables, then flatten the emissions from those into a single Observable
usingFlatMap(){
this.obs
.flatMap(x => Observable.timer(500).map(() => x + 10))// transform the input wrapping it with another observable and addition of 10
.subscribe(
x => console.log(x),
err => console.error(err),
() => console.log('done completed')
);
}
obs = Observable.of(1, 2, 3, 4);
ngOnInit() {
this.usingMap();
this.usingMapToMakeInnerObservable();
this.usingMapAndMergeAll();
this.usingFlatMap();
}usingMap() {
this.obs
.map(x => x * 2) // transform the input by multiple of 2
.subscribe(
x => console.log(x),
err => console.error(err),
() => console.log('done completed')
);
}
usingMapToMakeInnerObservable() {
this.obs
.map(x => Observable.timer(500).map(() => x + 3)) // transform the input wrapping it with another observable and addition of 3
//.map(x => Observable.timer(500).map((x) => x + 3)) // !!! REMEMBER Not the same as the immediate above
.subscribe(
x => console.log(x),
err => console.error(err),
() => console.log('done completed')
);
}
// Map and Merge all is the same as just one FlatMap
usingMapAndMergeAll() {
this.obs
.map(x => Observable.timer(500).map(() => x + 3)) // transform the input wrapping it with another observable and addition of 3
.mergeAll()
.subscribe(
x => console.log(x),
err => console.error(err),
() => console.log('done completed')
);
}
// Flat map is the same as map then merge all
// transform the items emitted by an Observable into Observables, then flatten the emissions from those into a single Observable
usingFlatMap() {
this.obs
.flatMap(x => Observable.timer(500).map(() => x + 10)) // transform the input wrapping it with another observable and addition of 10
.subscribe(
x => console.log(x),
err => console.error(err),
() => console.log('done completed')
);
}
obs = Observable.of(1, 2, 3, 4);
ngOnInit() {
this.usingMap();
this.usingMapToMakeInnerObservable();
this.usingMapAndMergeAll();
this.usingFlatMap();
}usingMap() {
this.obs
.map(x => x * 2) // transform the input by multiple of 2
.subscribe(
x => console.log(x),
err => console.error(err),
() => console.log('done completed')
);
}
usingMapToMakeInnerObservable() {
this.obs
.map(x => Observable.timer(500).map(() => x + 3)) // transform the input wrapping it with another observable and addition of 3
//.map(x => Observable.timer(500).map((x) => x + 3)) // !!! REMEMBER Not the same as the immediate above
.subscribe(
x => console.log(x),
err => console.error(err),
() => console.log('done completed')
);
}
// Map and Merge all is the same as just one FlatMap
usingMapAndMergeAll() {
this.obs
.map(x => Observable.timer(500).map(() => x + 3)) // transform the input wrapping it with another observable and addition of 3
.mergeAll()
.subscribe(
x => console.log(x),
err => console.error(err),
() => console.log('done completed')
);
}
// Flat map is the same as map then merge all
// transform the items emitted by an Observable into Observables, then flatten the emissions from those into a single Observable
usingFlatMap() {
this.obs
.flatMap(x => Observable.timer(500).map(() => x + 10)) // transform the input wrapping it with another observable and addition of 10
.subscribe(
x => console.log(x),
err => console.error(err),
() => console.log('done completed')
);
}
Transforming pure Javascript array vs. Observable from array
* This keeps creating new array. It is good that it creates new array of arr for immutability.
* But it's bad because there is clean up and resource intensive for mobile
* */
transformArray(){
let result = this.array
.filter(( x, i, arr ) =>{
console.log('filtering ' + x);
console.log('is the source array ' + (arr === this.array));
return x
})
.map(( x, i, arr ) =>{
console.log('mapping ' + x);
console.log('is the source array ' + (arr === this.array));
return x + '!';
})
.reduce(( r, x, i, arr ) =>{
console.log('reducing ' + x);
return r + x;
}, '--');
console.log(result);
}
/*
* This is more efficient for resource management because it linearly scans and discard when not right
* */
setArrayToObservableThenTransform(){ let obsArray = Observable.from(this.array); // Use Observable.from() instead of Observable.of(). There is diff. obsArray
.filter(( x: any ) =>{
console.log('filtering ' + x);
return x
})
.map(( x ) =>{
console.log('mapping ' + x);
return x + '!';
})
.reduce(( r, x ) =>{
console.log('reducing ' + x);
return r + x;
}, '--')
.subscribe(
x => console.log(x)
);
}
array = [0, 1, 2, 3, 4, 5];
ngOnInit() {
this.setArrayToObservableThenTransform();
}/*
* This keeps creating new array. It is good that it creates new array of arr for immutability.
* But it's bad because there is clean up and resource intensive for mobile
* */
transformArray() {
let result = this.array
.filter(( x, i, arr ) => {
console.log('filtering ' + x);
console.log('is the source array ' + (arr === this.array));
return x
})
.map(( x, i, arr ) => {
console.log('mapping ' + x);
console.log('is the source array ' + (arr === this.array));
return x + '!';
})
.reduce(( r, x, i, arr ) => {
console.log('reducing ' + x);
return r + x;
}, '--');
console.log(result);
}
/*
* This is more efficient for resource management because it linearly scans and discard when not right
* */
setArrayToObservableThenTransform() { let obsArray = Observable.from(this.array); // Use Observable.from() instead of Observable.of(). There is diff. obsArray
.filter(( x: any ) => {
console.log('filtering ' + x);
return x
})
.map(( x ) => {
console.log('mapping ' + x);
return x + '!';
})
.reduce(( r, x ) => {
console.log('reducing ' + x);
return r + x;
}, '--')
.subscribe(
x => console.log(x)
);
}
array = [0, 1, 2, 3, 4, 5];
ngOnInit() {
this.setArrayToObservableThenTransform();
}/*
* This keeps creating new array. It is good that it creates new array of arr for immutability.
* But it's bad because there is clean up and resource intensive for mobile
* */
transformArray() {
let result = this.array
.filter(( x, i, arr ) => {
console.log('filtering ' + x);
console.log('is the source array ' + (arr === this.array));
return x
})
.map(( x, i, arr ) => {
console.log('mapping ' + x);
console.log('is the source array ' + (arr === this.array));
return x + '!';
})
.reduce(( r, x, i, arr ) => {
console.log('reducing ' + x);
return r + x;
}, '--');
console.log(result);
}
/*
* This is more efficient for resource management because it linearly scans and discard when not right
* */
setArrayToObservableThenTransform() { let obsArray = Observable.from(this.array); // Use Observable.from() instead of Observable.of(). There is diff. obsArray
.filter(( x: any ) => {
console.log('filtering ' + x);
return x
})
.map(( x ) => {
console.log('mapping ' + x);
return x + '!';
})
.reduce(( r, x ) => {
console.log('reducing ' + x);
return r + x;
}, '--')
.subscribe(
x => console.log(x)
);
}
val => console.log('scanObservableArray() ' + val)
);
}
array = [0, 1, 2, 3, 4, 5];ngOnInit() {
this.reduceArray();
this.reduceObservableArray();
this.reduceObservableArray_Abstract2();
this.scanObservableArray();
}
/*
* This is the same as reduceObservableArray()
* */
reduceArray() {
let result = this.array.reduce(
(accumulator, currentValue) => accumulator + currentValue, 3
); // 3 is the init value.
console.log('reduceArray ' + result); // output 18 => 3 + (0 ... 5)
}
/*
* This is the same as reduceArray()
* But this waits for all the arrays to finish emitting before reducing them to one single number
* See the next method to understand better
* */
reduceObservableArray() {
let obsArray = Observable.from(this.array);
obsArray.reduce(
(accumulator, currentValue) => accumulator + currentValue, 3
).subscribe(
val => console.log('reduceObservableArray ' + val)
);
}
/*
* The exact same reduce function/method as of reduceObserableArray() above
* This proves that it waits for all 6 numbers to come in then reduce them
* */
reduceObservableArray_Abstract2() {
let obsArray = Observable.interval(1000).take(6); //emits 6 times of 0, 1, 2, 3, 4, 5
obsArray.reduce(
(accumulator, currentValue) => accumulator + currentValue, 3
).subscribe(
val => console.log('reduceObservableArray_Abstract2 ' + val)
);
}
/*
* This is the same as the above reduceObserableArray_Abstract2()
* except this is using scan instead of reduce
* */
scanObservableArray() {
let obsArray = Observable.interval(1000).take(6); //emits 6 times of 0, 1, 2, 3, 4, 5
obsArray.scan(
(accumulator, currentValue) => accumulator + currentValue, 3
).subscribe(
val => console.log('scanObservableArray() ' + val)
);
}
array = [0, 1, 2, 3, 4, 5];ngOnInit() {
this.reduceArray();
this.reduceObservableArray();
this.reduceObservableArray_Abstract2();
this.scanObservableArray();
}
/*
* This is the same as reduceObservableArray()
* */
reduceArray() {
let result = this.array.reduce(
(accumulator, currentValue) => accumulator + currentValue, 3
); // 3 is the init value.
console.log('reduceArray ' + result); // output 18 => 3 + (0 ... 5)
}
/*
* This is the same as reduceArray()
* But this waits for all the arrays to finish emitting before reducing them to one single number
* See the next method to understand better
* */
reduceObservableArray() {
let obsArray = Observable.from(this.array);
obsArray.reduce(
(accumulator, currentValue) => accumulator + currentValue, 3
).subscribe(
val => console.log('reduceObservableArray ' + val)
);
}
/*
* The exact same reduce function/method as of reduceObserableArray() above
* This proves that it waits for all 6 numbers to come in then reduce them
* */
reduceObservableArray_Abstract2() {
let obsArray = Observable.interval(1000).take(6); //emits 6 times of 0, 1, 2, 3, 4, 5
obsArray.reduce(
(accumulator, currentValue) => accumulator + currentValue, 3
).subscribe(
val => console.log('reduceObservableArray_Abstract2 ' + val)
);
}
/*
* This is the same as the above reduceObserableArray_Abstract2()
* except this is using scan instead of reduce
* */
scanObservableArray() {
let obsArray = Observable.interval(1000).take(6); //emits 6 times of 0, 1, 2, 3, 4, 5
obsArray.scan(
(accumulator, currentValue) => accumulator + currentValue, 3
).subscribe(
val => console.log('scanObservableArray() ' + val)
);
}
Create, next, and subscribe to Subject and BehaviorSubject
There is a Stack Overflow thread which discussed about the difference between Subject and BehaviorSubject. It’s worth understanding.
import { BehaviorSubject } from "rxjs/BehaviorSubject";
// create subject
// there is no need for initial value
subject = new Subject<boolean>();
// create behaviorSubject which require initial value
// true is an initial value. if there is a subscription
// after this, it would get true value immediately
behaviorSubject = new BehaviorSubject<boolean>(true);
ngOnInit(){
this.subject.next(false); /* Subject subscription wont get anything at this point before the subscribeSubject() */this.subscribeSubject();
this.subscribeBehaviorSubject();
}
/*
* Push the next val into the behavior subject
* */
nextSubject(val: boolean){
this.subject.next(val);
}
/*
* Any values push into the subject would not be can shown
* before this subscribeSubject() is called
* */
subscribeSubject(){
this.subject
//.take(1) //when we include .take(1) we will have a complete. Without this it will continue subscribing
.subscribe(
val => console.log(val),
err => console.error(err),
() => console.log('completed')
);
}
/*
* This is the proper way to return a subject as observable
* */
getSubject(): Observable<boolean>{
returnthis.subject.asObservable();
}
/*
* Push the next val into the behavior subject
* */
nextBehaviorSubject(val: boolean){
this.behaviorSubject.next(val);
}
/*
* For angular Behavior subject for a data service as a angular service often initializes
* before component and behavior subject ensures that the component consuming the
* service receives the last updated data even if there are no new
* updates since the component's subscription to this data.
* */
subscribeBehaviorSubject(){
this.behaviorSubject
// .first()
.subscribe(
val => console.log(val),
err => console.error(err),
() => console.log('completed')
);
}
import { Subject } from 'rxjs/Subject';
import { BehaviorSubject } from "rxjs/BehaviorSubject";
// create subject
// there is no need for initial value
subject = new Subject<boolean>();
// create behaviorSubject which require initial value
// true is an initial value. if there is a subscription
// after this, it would get true value immediately
behaviorSubject = new BehaviorSubject<boolean>(true);
ngOnInit() {
this.subject.next(false); /* Subject subscription wont get anything at this point before the subscribeSubject() */ this.subscribeSubject();
this.subscribeBehaviorSubject();
}
/*
* Push the next val into the behavior subject
* */
nextSubject(val: boolean) {
this.subject.next(val);
}
/*
* Any values push into the subject would not be can shown
* before this subscribeSubject() is called
* */
subscribeSubject() {
this.subject
//.take(1) //when we include .take(1) we will have a complete. Without this it will continue subscribing
.subscribe(
val => console.log(val),
err => console.error(err),
() => console.log('completed')
);
}
/*
* This is the proper way to return a subject as observable
* */
getSubject(): Observable<boolean> {
return this.subject.asObservable();
}
/*
* Push the next val into the behavior subject
* */
nextBehaviorSubject(val: boolean) {
this.behaviorSubject.next(val);
}
/*
* For angular Behavior subject for a data service as a angular service often initializes
* before component and behavior subject ensures that the component consuming the
* service receives the last updated data even if there are no new
* updates since the component's subscription to this data.
* */
subscribeBehaviorSubject() {
this.behaviorSubject
// .first()
.subscribe(
val => console.log(val),
err => console.error(err),
() => console.log('completed')
);
}
import { Subject } from 'rxjs/Subject';
import { BehaviorSubject } from "rxjs/BehaviorSubject";
// create subject
// there is no need for initial value
subject = new Subject<boolean>();
// create behaviorSubject which require initial value
// true is an initial value. if there is a subscription
// after this, it would get true value immediately
behaviorSubject = new BehaviorSubject<boolean>(true);
ngOnInit() {
this.subject.next(false); /* Subject subscription wont get anything at this point before the subscribeSubject() */ this.subscribeSubject();
this.subscribeBehaviorSubject();
}
/*
* Push the next val into the behavior subject
* */
nextSubject(val: boolean) {
this.subject.next(val);
}
/*
* Any values push into the subject would not be can shown
* before this subscribeSubject() is called
* */
subscribeSubject() {
this.subject
//.take(1) //when we include .take(1) we will have a complete. Without this it will continue subscribing
.subscribe(
val => console.log(val),
err => console.error(err),
() => console.log('completed')
);
}
/*
* This is the proper way to return a subject as observable
* */
getSubject(): Observable<boolean> {
return this.subject.asObservable();
}
/*
* Push the next val into the behavior subject
* */
nextBehaviorSubject(val: boolean) {
this.behaviorSubject.next(val);
}
/*
* For angular Behavior subject for a data service as a angular service often initializes
* before component and behavior subject ensures that the component consuming the
* service receives the last updated data even if there are no new
* updates since the component's subscription to this data.
* */
subscribeBehaviorSubject() {
this.behaviorSubject
// .first()
.subscribe(
val => console.log(val),
err => console.error(err),
() => console.log('completed')
);
}
.finally(() => console.log('End of the observable, Hello World'))
.subscribe(
val => console.log('count taker ' + val)
);
}
usingFinallyOperator() {
Observable
.interval(500)
.take(4)
.finally(() => console.log('End of the observable, Hello World'))
.subscribe(
val => console.log('count taker ' + val)
);
}
usingFinallyOperator() {
Observable
.interval(500)
.take(4)
.finally(() => console.log('End of the observable, Hello World'))
.subscribe(
val => console.log('count taker ' + val)
);
}
Stopping / Intercepting Observable
Imagine using Gmail where it allows you to undo email sent? We can produce similar experience with Observable
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// subscription is created when an observable is being subscribed
subscription: Subscription;
// boolean variable for showing stop observable using takeWhile operator
isTrue: boolean = true;
/*
* basic interval can be used as delay too
* Imagine Gmail allows you to send and undo send within 4 seconds of sending
* Use Case: Perform an action 8 seconds later then intercept if user choose to undo the action
* */basicInterval(){
let undoInSeconds: number = 8;
this.subscription = Observable
.interval(1000)
.take(undoInSeconds)
.takeWhile(() =>this.isTrue)
.subscribe(
(val: number) =>{
console.log(`${val + 1} seconds... UNDO`);
( val === (undoInSeconds - 1)) ? console.log('Email sent / Action performed'):null;
}
);
}
/*
* This is to stop observable from continuing performance
* Use Case: Stop observable from running like how Gmail could undo email being sent
* */stopObservableUsingUnsubscribe(){
if(!!this.subscription){
this.subscription.unsubscribe();
console.log('subscription: Subscription is unsubscribed');
}
}
/*
* This is also to stop observable from continuing performance
* This method is more preferable than subscribing method then unsubscribe
* Use Case: Stop observable from running like how Gmail could undo email being sent
* */stopObservableUsingTakeWhile(){
this.isTrue = false;
}
// subscription is created when an observable is being subscribed
subscription: Subscription;
// boolean variable for showing stop observable using takeWhile operator
isTrue: boolean = true;
/*
* basic interval can be used as delay too
* Imagine Gmail allows you to send and undo send within 4 seconds of sending
* Use Case: Perform an action 8 seconds later then intercept if user choose to undo the action
* */basicInterval() {
let undoInSeconds: number = 8;
this.subscription = Observable
.interval(1000)
.take(undoInSeconds)
.takeWhile(() => this.isTrue)
.subscribe(
(val: number) => {
console.log(`${val + 1} seconds... UNDO`);
( val === (undoInSeconds - 1) ) ? console.log('Email sent / Action performed') : null;
}
);
}
/*
* This is to stop observable from continuing performance
* Use Case: Stop observable from running like how Gmail could undo email being sent
* */stopObservableUsingUnsubscribe() {
if (!!this.subscription) {
this.subscription.unsubscribe();
console.log('subscription: Subscription is unsubscribed');
}
}
/*
* This is also to stop observable from continuing performance
* This method is more preferable than subscribing method then unsubscribe
* Use Case: Stop observable from running like how Gmail could undo email being sent
* */stopObservableUsingTakeWhile() {
this.isTrue = false;
}
// subscription is created when an observable is being subscribed
subscription: Subscription;
// boolean variable for showing stop observable using takeWhile operator
isTrue: boolean = true;
/*
* basic interval can be used as delay too
* Imagine Gmail allows you to send and undo send within 4 seconds of sending
* Use Case: Perform an action 8 seconds later then intercept if user choose to undo the action
* */basicInterval() {
let undoInSeconds: number = 8;
this.subscription = Observable
.interval(1000)
.take(undoInSeconds)
.takeWhile(() => this.isTrue)
.subscribe(
(val: number) => {
console.log(`${val + 1} seconds... UNDO`);
( val === (undoInSeconds - 1) ) ? console.log('Email sent / Action performed') : null;
}
);
}
/*
* This is to stop observable from continuing performance
* Use Case: Stop observable from running like how Gmail could undo email being sent
* */stopObservableUsingUnsubscribe() {
if (!!this.subscription) {
this.subscription.unsubscribe();
console.log('subscription: Subscription is unsubscribed');
}
}
/*
* This is also to stop observable from continuing performance
* This method is more preferable than subscribing method then unsubscribe
* Use Case: Stop observable from running like how Gmail could undo email being sent
* */stopObservableUsingTakeWhile() {
this.isTrue = false;
}
Perform conditional Reactive Form validation
This is my approach to performing conditional validation when using Angular. We will minimally manipulate the Observable of RxJS in this example. Let’s try by creating or using app.component.ts.
1 . Create a form that has two form controls reason and otherReason.
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
/* * Refer to angular official guide at https://angular.io/guide/reactive-forms on how to create reactive form with form controls * */createForm(){
/* * Refer to angular official guide at https://angular.io/guide/reactive-forms on how to create reactive form with form controls * */createForm() {
this.form = this.formBuilder.group({ reason: ['', Validators.required ],
otherReason: [''], });
}
/* * Refer to angular official guide at https://angular.io/guide/reactive-forms on how to create reactive form with form controls * */createForm() {
this.form = this.formBuilder.group({ reason: ['', Validators.required ],
otherReason: [''], });
}
2 . Create two methods addValidator and removeValidator.
The outcome should illustrate that if we select ‘Others’ option in the dropdown list of reason, it should make otherReason form control field as required.
Drop-down selection for reason e.g. reason for absent from workOn selecting Others, the conditional form validation would make otherReason field turn into a required field
Perform manual operations after reading from Firebase database
When using AngularFire2 with Angular + Firebase, in getting a list of data from Firebase, we will get one observable instance but within that one observable is an array of N size. We can manually filter the array inside that one observable instance using arr.filter. It is different from RxJS .filter operator. Of course we can also flatten what is inside an array using .flatMap() operator. However, we’re going use JavaScript array filtering, instead of non-observable filtering, right after getting an observable object.
We can also reverse an array using JavaScript array reverse function. See reference. On a side note, using negative timestamp to reverse Firebase display is also another option.
Besides those above, we can also use the response returned from AngularFire2 to perform “local filter/search”. This result can be valuable for autocomplete filtered list or searches. However, this approach below suffers severely in performance issue where at the magnitude of the size of the returned response from AngularFire2. E.g. if the list has N items. It has to iterate at least 1N. Perhaps an average of 2N.
The task of creating an error free program is not easy. And, if your program runs free of errors, keeping it error-free after an update or change is even more complicated. You don’t want to insert new errors or change correct code with wrong parts.
The answer to this situation (directly from the Oracle of Delphi) is: Testing, Testing, Testing
And the best way to test is to start with tests.
This means: think about what the result should be and then create a Test that checks this. Imagine, you have to write a function for adding two values, and you should describe the functionality.
So, maybe, your description contains one or two examples:
My functions add’s two numbers, e.g 5 plus 7 is 12 (or at least should be 12 :))
The procedure with the TDD is:
think and define, what the function should to
write a stub for the function, e.g. only function parameters and return type
write a function, that tests you function with defines parameters and know result
For our example above, this means:
Write the python script with the desired functionality: src/main.py
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
defadd(val1,val2):
return0# this is only a dummy return value
def add(val1,val2):
return 0 # this is only a dummy return value
def add(val1,val2):
return 0 # this is only a dummy return value
Write the Python Testscript: tst/main.p
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
def_test_add():
result = add(5,7)
if(result = 12):
print("everything fine")
else:
printf("ups, problems with base arithmetics")
def_test_add():
result = add(5,7)
if (result = 12):
print("everything fine")
else:
printf("ups, problems with base arithmetics")
def_test_add():
result = add(5,7)
if (result = 12):
print("everything fine")
else:
printf("ups, problems with base arithmetics")
Now, with these in your toolbox, you can always verify your code by running the tests.
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
$ python test_add.py
ups, problems with base arithmetics
$ python test_add.py
ups, problems with base arithmetics
$ python test_add.py
ups, problems with base arithmetics
dfdf
Setup virtual environment
Mostly, tests are repeated after every change. So, to be sure, that each test is running the same way and with the same environment, we will use pythons virtual environment feature to create a new fresh python environment for the tests.
Create virtual environment
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
$ python3 -m venv .env/python
$ python3 -m venv .env/python
$ python3 -m venv .env/python
Activate environment
Add the following line to .bashrc (or .envrc if you are using direnv)
At least, create a simple Calculator: src/CalculatorLib/Calculator.py
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
class Calculator:
def__init__(self):
print("Init Calculator")
defadd(self, a, b):
return a + b
defsubtract(self, a, b):
return a - b
defmultiply(self, a, b):
return a * b
defdivide(self, a, b):
return a / b
defpower(self, base, exp):
return base ** exp
class Calculator:
def __init__(self):
print("Init Calculator")
def add(self, a, b):
return a + b
def subtract(self, a, b):
return a - b
def multiply(self, a, b):
return a * b
def divide(self, a, b):
return a / b
def power(self, base, exp):
return base ** exp
class Calculator:
def __init__(self):
print("Init Calculator")
def add(self, a, b):
return a + b
def subtract(self, a, b):
return a - b
def multiply(self, a, b):
return a * b
def divide(self, a, b):
return a / b
def power(self, base, exp):
return base ** exp
Create the Main App for your Calculator: src/main.py
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
from CalculatorLib.Calculator import Calculator
classMain(object):
defrun(self):
c = Calculator()
print("5 + 3 =
print("8 - 4 =
print("5 * 3 =
print("8 / 4 =
print("8 ^ 4 =
if __name__ == '__main__':
Main().run()
from CalculatorLib.Calculator import Calculator
class Main(object):
def run(self):
c = Calculator()
print("5 + 3 =
print("8 - 4 =
print("5 * 3 =
print("8 / 4 =
print("8 ^ 4 =
if __name__ == '__main__':
Main().run()
from CalculatorLib.Calculator import Calculator
class Main(object):
def run(self):
c = Calculator()
print("5 + 3 =
print("8 - 4 =
print("5 * 3 =
print("8 / 4 =
print("8 ^ 4 =
if __name__ == '__main__':
Main().run()
Yur done with the fist development step. Try your app:
</pre><p>Recognize the ModuleNotFoundError in line 16! This means, that Python could not find the desired CalculatorLib.</p><p>Look at your folder structure:</p><pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">$ tree .
.
├── src
│ ├── CalculatorLib
│ │ ├── Calculator.py
│ │ ├── init__.py
│ └── main.py
└── tst
└── main.py</pre><div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-flow wp-block-group-is-layout-flow"><p>.</p></div></div><p>In your Testscript, we import the CalculatorLib whit this statement:</p><pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">from CalculatorLib.Calculator import Calculator</pre><p>Python is interpreting thisin the following way:</p><ul class="wp-block-list"><li>Look in the folder of the test script for a subfolder with the name CalculatorLib</li><li>There, look for a file <code>Calculator.py</code></li><li>And inthis file, use the class Calculator</li></ul><p>Obviously, the folder <code>CalculatorLib</code> is NOT in the same folder as the test script: it is part of the <code>src</code> folder.</p><p>So, using the environment variable <code>PYTHONPATH</code>, we inform python where to search python scripts and folders.</p><h2 class="wp-block-heading">Add additional functionality</h2><p>Add a function at the endof your Calculator: <code>src/CalculatorLib/Calculator.py</code></p><pre class="EnlighterJSRAW" data-enlighter-language="python" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group=""> ....
deffactorial(self, n):
return0</pre><p>Add a call of the newfunction to your main app: <code>src/main.py</code></p><pre class="EnlighterJSRAW" data-enlighter-language="python" data-enlighter-theme="" data-enlighter-highlight="4" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group=""> ...
defrun(self):
...
print("4! =
<p>Add a test for the new function to your test script: <code>tst/main.py</code></p>
============================= 1 failed, 5 passed in 0.14 seconds =============================</pre><p>Test failed, was we expect it.</p><p>Now, implement the function correctly and startover the test:</p><p>Add a function at the end of your Calculator: <code>src/CalculatorLib/Calculator.py</code></p><pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import math
self.assertEqual(self.given.photo, 'photo')</pre><h4 class="wp-block-heading"><a href="http://radish-bdd.io/">radsh is not just another BDD tool …THE ROOT FROM RED TO GREEN</a></h4><pre class="EnlighterJSRAW" data-enlighter-language="python" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">from radish import given, when, then
@given("I have the numbers {number1:g} and {number2:g}")
OK</pre><h2 class="wp-block-heading">Links and additional information</h2><p><a href="http://pythontesting.net/">http://pythontesting.net/</a></p><p><a href="https://www.xenonstack.com/blog/test-driven-development-big-data">https://www.xenonstack.com/blog/test-driven-development-big-data</a>/</p><p><a href="https://realpython.com/python-testing/">https://realpython.com/python-testing/</a></p></pre>
PYTHONPATH=./srcpython−mpytesttst/main.py−−verbose=================================testsessionstarts================================platformdarwin−−Python3.7.4,pytest−4.4.1,py−1.8.0,pluggy−0.9.0−−<TestprojectPython−Calculator/.env/python/bin/python>cachedir:.pytestcacherootdir:<TestprojectPython−Calculator>plugins:cov−2.6.1collected5itemstst/main.py::CalculatorTest::testaddPASSED[20tst/main.py::CalculatorTest::testdividePASSED[40tst/main.py::CalculatorTest::testmultiplyPASSED[60tst/main.py::CalculatorTest::testpowerPASSED[80tst/main.py::CalculatorTest::testsubtractPASSED[100<p>Thecommandtorunthetestis<code>python−mpytesttst/main.py,</code>butwhytheleadVariable<code>PYTHONPATH</code>?</p><p>Tryitwithout:</p><preclass="EnlighterJSRAW"data−enlighter−language="shell"data−enlighter−theme=""data−enlighter−highlight=""data−enlighter−linenumbers=""data−enlighter−lineoffset=""data−enlighter−title=""data−enlighter−group=""> python -m pytest tst/main.py
=================================== test session starts ==================================
platform darwin -- Python 3.7.4, pytest-4.4.1, py-1.8.0, pluggy-0.9.0 -- ##/Testproject_Python-Calculator/.env/python/bin/python
cachedir: .pytest_cache
rootdir: ##/Testproject_Python-Calculator
plugins: cov-2.6.1
collected 0 items / 1 errors
========================================= ERRORS =========================================
____________________________________ ERROR collecting tst/main.py ________________________
ImportError while importing test module '##/Testproject_Python-Calculator/tst/main.py'.
Hint: make sure your test modules/packages have valid Python names.
Traceback:
tst/main.py:2: in <module>
from CalculatorLib.Calculator import Calculator
E ModuleNotFoundError: No module named 'CalculatorLib'
!!!!!!!!!!!!!!!!!!!!!!!!! Interrupted: 1 errors during collection !!!!!!!!!!!!!!!!!!!!!!!!
================================== 1 error in 1.84 secon==================================
</pre><p>Recognize the ModuleNotFoundError in line 16! This means, that Python could not find the desired CalculatorLib.</p><p>Look at your folder structure:</p><pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">tree . . ├── src │ ├── CalculatorLib │ │ ├── Calculator.py │ │ ├── init__.py │ └── main.py └── tst └── main.py</pre><div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-flow wp-block-group-is-layout-flow"><p>.</p></div></div><p>In your Testscript, we import the CalculatorLib whit this statement:</p><pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">from CalculatorLib.Calculator import Calculator</pre><p>Python is interpreting this in the following way:</p><ul class="wp-block-list"><li>Look in the folder of the test script for a subfolder with the name CalculatorLib</li><li>There, look for a file <code>Calculator.py</code></li><li>And in this file, use the class Calculator</li></ul><p>Obviously, the folder <code>CalculatorLib</code> is NOT in the same folder as the test script: it is part of the <code>src</code> folder.</p><p>So, using the environment variable <code>PYTHONPATH</code>, we inform python where to search python scripts and folders.</p><h2 class="wp-block-heading">Add additional functionality</h2><p>Add a function at the end of your Calculator: <code>src/CalculatorLib/Calculator.py</code></p><pre class="EnlighterJSRAW" data-enlighter-language="python" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group=""> .... def factorial(self, n): return 0</pre><p>Add a call of the new function to your main app: <code>src/main.py</code></p><pre class="EnlighterJSRAW" data-enlighter-language="python" data-enlighter-theme="" data-enlighter-highlight="4" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group=""> ... def run(self): ... print("4! =
<p>Add a test for the new function to your test script: <code>tst/main.py</code></p>
def test_factorial(self): > self.assertEqual(24, self.c.factorial(4)) E AssertionError: 24 != 0
tst/main.py:31: AssertionError ============================= 1 failed, 5 passed in 0.14 seconds =============================</pre><p>Test failed, was we expect it.</p><p>Now, implement the function correctly and startover the test:</p><p>Add a function at the end of your Calculator: <code>src/CalculatorLib/Calculator.py</code></p><pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import math
class Calculator: ... def factorial(self, n): if not n >= 0: raise ValueError("n must be >= 0")
if math.floor(n) != n: raise ValueError("n must be exact integer")
if n+1 == n: # catch a value like 1e300 raise OverflowError("n too large")
@mango.then('I see profile') def then_profile(): self.assertEqual(self.given.profile, 'profile')
@mango.then('I see my photo') def then_photo(): self.assertEqual(self.given.photo, 'photo')</pre><h4 class="wp-block-heading"><a href="http://radish-bdd.io/">radsh is not just another BDD tool …THE ROOT FROM RED TO GREEN</a></h4><pre class="EnlighterJSRAW" data-enlighter-language="python" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">from radish import given, when, then
@given("I have the numbers {number1:g} and {number2:g}") def have_numbers(step, number1, number2): step.context.number1 = number1 step.context.number2 = number2
@then("I expect the result to be {result:g}") def expect_result(step, result): assert step.context.result == result</pre><h4 class="wp-block-heading"><a href="https://docs.python.org/3.7/library/doctest.html">doctest</a></h4><pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">""" The example module supplies one function, factorial(). For example,
>>> factorial(5) 120 """
def factorial(n): """Return the factorial of n, an exact integer >= 0.
>>> [factorial(n) for n in range(6)] [1, 1, 2, 6, 24, 120] >>> factorial(30) 265252859812191058636308480000000 >>> factorial(-1) Traceback (most recent call last): ... ValueError: n must be >= 0
Factorials of floats are OK, but the float must be an exact integer: >>> factorial(30.1) Traceback (most recent call last): ... ValueError: n must be exact integer >>> factorial(30.0) 265252859812191058636308480000000
It must also not be ridiculously large: >>> factorial(1e100) Traceback (most recent call last): ... OverflowError: n too large """
import math if not n >= 0: raise ValueError("n must be >= 0") if math.floor(n) != n: raise ValueError("n must be exact integer") if n+1 == n: # catch a value like 1e300 raise OverflowError("n too large") result = 1 factor = 2 while factor <= n: result *= factor factor += 1 return result
if __name__ == "__main__": import doctest doctest.testmod()</pre><h2 class="wp-block-heading">Sample Session with Test Frameworks</h2><pre class="EnlighterJSRAW" data-enlighter-language="shell" data-enlighter-theme="" data-enlighter-highlight="1" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group=""> py.test -v
========================================================= test session starts ==========================================================
platform darwin -- Python 3.7.3, pytest-4.3.1, py-1.8.0, pluggy-0.9.0 -- /CLOUD/Development.Anaconda/anaconda3/bin/python
cachedir: .pytest_cache
rootdir: /CLOUD/Development.Python/Repositories.FromGithub/repositories/python-toolbox/Working-with-TDD/app, inifile:
plugins: remotedata-0.3.1, openfiles-0.3.2, doctestplus-0.3.0, arraydiff-0.3
collected 4 items
test_base.py::test_should_pass PASSED [ 25
test_base.py::test_should_raise_error PASSED [ 50
test_base.py::test_check_if_true_is_true PASSED [ 75
test_base.py::test_check_if_inc_works PASSED</pre><pre class="EnlighterJSRAW" data-enlighter-language="shell" data-enlighter-theme="" data-enlighter-highlight="1" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">$ nosetests -v
test_base.test_should_pass ... ok
test_base.test_should_raise_error ... ok
test_base.test_check_if_true_is_true ... ok
test_base.test_check_if_inc_works ... ok
----------------------------------------------------------------------
Ran 4 tests in 0.001s
OK</pre><h2 class="wp-block-heading">Links and additional information</h2><p><a href="http://pythontesting.net/">http://pythontesting.net/</a></p><p><a href="https://www.xenonstack.com/blog/test-driven-development-big-data">https://www.xenonstack.com/blog/test-driven-development-big-data</a>/</p><p><a href="https://realpython.com/python-testing/">https://realpython.com/python-testing/</a></p></pre>
============================= 1 failed, 5 passed in 0.14 seconds =============================</pre><p>Test failed, was we expect it.</p><p>Now, implement the function correctly and startover the test:</p><p>Add a function at the end of your Calculator: <code>src/CalculatorLib/Calculator.py</code></p><pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import math
self.assertEqual(self.given.photo, 'photo')</pre><h4 class="wp-block-heading"><a href="http://radish-bdd.io/">radsh is not just another BDD tool …THE ROOT FROM RED TO GREEN</a></h4><pre class="EnlighterJSRAW" data-enlighter-language="python" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">from radish import given, when, then
@given("I have the numbers {number1:g}and{number2:g}")
Now, implement the function correctly and startover the test:
Add a function at the end of your Calculator: src/CalculatorLib/Calculator.py
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
import math
class Calculator:
...
deffactorial(self, n):
if not n >= 0:
raise ValueError("n must be >= 0")
if math.floor(n) != n:
raise ValueError("n must be exact integer")
if n+1 == n: # catch a value like 1e300
raise OverflowError("n too large")
result, factor = 1, 2
while factor <= n:
result *= factor
factor += 1
return result
import math
class Calculator:
...
def factorial(self, n):
if not n >= 0:
raise ValueError("n must be >= 0")
if math.floor(n) != n:
raise ValueError("n must be exact integer")
if n+1 == n: # catch a value like 1e300
raise OverflowError("n too large")
result, factor = 1, 2
while factor <= n:
result *= factor
factor += 1
return result
import math
class Calculator:
...
def factorial(self, n):
if not n >= 0:
raise ValueError("n must be >= 0")
if math.floor(n) != n:
raise ValueError("n must be exact integer")
if n+1 == n: # catch a value like 1e300
raise OverflowError("n too large")
result, factor = 1, 2
while factor <= n:
result *= factor
factor += 1
return result
@given("I have the numbers {number1:g} and {number2:g}")
defhave_numbers(step, number1, number2):
step.context.number1 = number1
step.context.number2 = number2
@when("I sum them")
defsum_numbers(step):
step.context.result = step.context.number1 + \
step.context.number2
@then("I expect the result to be {result:g}")
defexpect_result(step, result):
assert step.context.result == result
from radish import given, when, then
@given("I have the numbers {number1:g} and {number2:g}")
def have_numbers(step, number1, number2):
step.context.number1 = number1
step.context.number2 = number2
@when("I sum them")
def sum_numbers(step):
step.context.result = step.context.number1 + \
step.context.number2
@then("I expect the result to be {result:g}")
def expect_result(step, result):
assert step.context.result == result
from radish import given, when, then
@given("I have the numbers {number1:g} and {number2:g}")
def have_numbers(step, number1, number2):
step.context.number1 = number1
step.context.number2 = number2
@when("I sum them")
def sum_numbers(step):
step.context.result = step.context.number1 + \
step.context.number2
@then("I expect the result to be {result:g}")
def expect_result(step, result):
assert step.context.result == result
The example module supplies one function, factorial(). For example,
>>> factorial(5)
120
"""
deffactorial(n):
"""Return the factorial of n, an exact integer >= 0.
>>> [factorial(n) for n in range(6)]
[1, 1, 2, 6, 24, 120]
>>> factorial(30)
265252859812191058636308480000000
>>> factorial(-1)
Traceback (most recent call last):
...
ValueError: n must be >= 0
Factorials of floats are OK, but the float must be an exact integer:
>>> factorial(30.1)
Traceback (most recent call last):
...
ValueError: n must be exact integer
>>> factorial(30.0)
265252859812191058636308480000000
It must also not be ridiculously large:
>>> factorial(1e100)
Traceback (most recent call last):
...
OverflowError: n too large
"""
import math
if not n >= 0:
raise ValueError("n must be >= 0")
if math.floor(n) != n:
raise ValueError("n must be exact integer")
if n+1 == n: # catch a value like 1e300
raise OverflowError("n too large")
result = 1
factor = 2
while factor <= n:
result *= factor
factor += 1
return result
if __name__ == "__main__":
import doctest
doctest.testmod()
"""
The example module supplies one function, factorial(). For example,
>>> factorial(5)
120
"""
def factorial(n):
"""Return the factorial of n, an exact integer >= 0.
>>> [factorial(n) for n in range(6)]
[1, 1, 2, 6, 24, 120]
>>> factorial(30)
265252859812191058636308480000000
>>> factorial(-1)
Traceback (most recent call last):
...
ValueError: n must be >= 0
Factorials of floats are OK, but the float must be an exact integer:
>>> factorial(30.1)
Traceback (most recent call last):
...
ValueError: n must be exact integer
>>> factorial(30.0)
265252859812191058636308480000000
It must also not be ridiculously large:
>>> factorial(1e100)
Traceback (most recent call last):
...
OverflowError: n too large
"""
import math
if not n >= 0:
raise ValueError("n must be >= 0")
if math.floor(n) != n:
raise ValueError("n must be exact integer")
if n+1 == n: # catch a value like 1e300
raise OverflowError("n too large")
result = 1
factor = 2
while factor <= n:
result *= factor
factor += 1
return result
if __name__ == "__main__":
import doctest
doctest.testmod()
"""
The example module supplies one function, factorial(). For example,
>>> factorial(5)
120
"""
def factorial(n):
"""Return the factorial of n, an exact integer >= 0.
>>> [factorial(n) for n in range(6)]
[1, 1, 2, 6, 24, 120]
>>> factorial(30)
265252859812191058636308480000000
>>> factorial(-1)
Traceback (most recent call last):
...
ValueError: n must be >= 0
Factorials of floats are OK, but the float must be an exact integer:
>>> factorial(30.1)
Traceback (most recent call last):
...
ValueError: n must be exact integer
>>> factorial(30.0)
265252859812191058636308480000000
It must also not be ridiculously large:
>>> factorial(1e100)
Traceback (most recent call last):
...
OverflowError: n too large
"""
import math
if not n >= 0:
raise ValueError("n must be >= 0")
if math.floor(n) != n:
raise ValueError("n must be exact integer")
if n+1 == n: # catch a value like 1e300
raise OverflowError("n too large")
result = 1
factor = 2
while factor <= n:
result *= factor
factor += 1
return result
if __name__ == "__main__":
import doctest
doctest.testmod()
Sample Session with Test Frameworks
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
$ py.test -v
========================================================= test session starts ==========================================================
platform darwin -- Python 3.7.3, pytest-4.3.1, py-1.8.0, pluggy-0.9.0 -- /CLOUD/Development.Anaconda/anaconda3/bin/python
$ nosetests -v
test_base.test_should_pass ... ok
test_base.test_should_raise_error ... ok
test_base.test_check_if_true_is_true ... ok
test_base.test_check_if_inc_works ... ok
----------------------------------------------------------------------
Ran 4 tests in 0.001s
OK
$ nosetests -v
test_base.test_should_pass ... ok
test_base.test_should_raise_error ... ok
test_base.test_check_if_true_is_true ... ok
test_base.test_check_if_inc_works ... ok
----------------------------------------------------------------------
Ran 4 tests in 0.001s
OK
* Environment: production
WARNING: This is a development server. Do not use it in a production deployment.
Use a production WSGI server instead.
* Debug mode: off
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
127.0.0.1 - - [01/Aug/2019 12:19:00] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [01/Aug/2019 12:19:00] "GET /favicon.ico HTTP/1.1" 404 -
* Environment: production
WARNING: This is a development server. Do not use it in a production deployment.
Use a production WSGI server instead.
* Debug mode: off
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
127.0.0.1 - - [01/Aug/2019 12:19:00] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [01/Aug/2019 12:19:00] "GET /favicon.ico HTTP/1.1" 404 -
We use cookies on our website to give you the most relevant experience by remembering your preferences and repeat visits. By clicking “Accept All”, you consent to the use of ALL the cookies. However, you may visit "Cookie Settings" to provide a controlled consent.
This website uses cookies to improve your experience while you navigate through the website. Out of these, the cookies that are categorized as necessary are stored on your browser as they are essential for the working of basic functionalities of the ...
Necessary cookies are absolutely essential for the website to function properly. These cookies ensure basic functionalities and security features of the website, anonymously.
Cookie
Duration
Description
cookielawinfo-checkbox-analytics
11 months
This cookie is set by GDPR Cookie Consent plugin. The cookie is used to store the user consent for the cookies in the category "Analytics".
cookielawinfo-checkbox-functional
11 months
The cookie is set by GDPR cookie consent to record the user consent for the cookies in the category "Functional".
cookielawinfo-checkbox-necessary
11 months
This cookie is set by GDPR Cookie Consent plugin. The cookies is used to store the user consent for the cookies in the category "Necessary".
cookielawinfo-checkbox-others
11 months
This cookie is set by GDPR Cookie Consent plugin. The cookie is used to store the user consent for the cookies in the category "Other.
cookielawinfo-checkbox-performance
11 months
This cookie is set by GDPR Cookie Consent plugin. The cookie is used to store the user consent for the cookies in the category "Performance".
viewed_cookie_policy
11 months
The cookie is set by the GDPR Cookie Consent plugin and is used to store whether or not user has consented to the use of cookies. It does not store any personal data.
Functional cookies help to perform certain functionalities like sharing the content of the website on social media platforms, collect feedbacks, and other third-party features.
Performance cookies are used to understand and analyze the key performance indexes of the website which helps in delivering a better user experience for the visitors.
Analytical cookies are used to understand how visitors interact with the website. These cookies help provide information on metrics the number of visitors, bounce rate, traffic source, etc.
Advertisement cookies are used to provide visitors with relevant ads and marketing campaigns. These cookies track visitors across websites and collect information to provide customized ads.