Русский
Русский
English
Статистика
Реклама

Блог компании otus. онлайн-образование

С безопасность для новичков

05.03.2021 18:07:04 | Автор: admin

Привет, хабровчане. Для будущих студентов курса "C++ Developer. Professional" Александр Колесников подготовил статью.

Приглашаем также посмотреть открытый вебинар на тему
Области видимости и невидимости. За 1,5 часа участники вместе с экспертом успеют реализовать класс общего назначения и запустить несколько unit-тестов с использованием googletest. Присоединяйтесь.


В статье на примерах будет рассмотрено, почему приложения на языке программирования С++ стоит разрабатывать с особым вниманием.

Сегодня язык программирования С++ существует в нескольких параллельных реальностях: C++98, C++11, C++14, C++17, C++20. Существует как минимум один источник, где можно немного разобраться со всем этим набором мультивселенных. Однако, когда дело дойдет до написания кода использования stackOverflow, вопрос а точно эта строка написана безопасно", будет мучать разработчика из релиза в релиз. Кстати, на момент написания статьи готовится новый стандарт С++23 =).

Откуда проблемы

С++ это весьма мощный язык программирования, который позволяет разрабатывать приложения для любого уровне операционной системы. Он существует уже не оно десятилетие и программист должен понимать, что делает, поскольку любая строка в коде может нести в себе довольно большое количество подводных камней, которые в будущем могут привести к проблемам с безопасностью кода. Каждое новое обновление увеличивает вероятность столкнуться с такими вещами.

Самые распространенные проблемы, с которыми может столкнуться новичок:

  • неверное объявление типов данных;

  • неверное использование выражений;

  • неправильная обработка целочисленных данных;

  • неправильная работа с контейнерами;

  • неправильная работа со строками;

  • неправильная работа с памятью;

  • неверная обработка exception;

  • пренебрежение ограничениями OOP;

  • состояния гонки при обработке ресурсов мультипоточным приложением;

  • прочие проблемы, о которых мало, где сказано.

Похоже, что если вчитаться в список ограничений, то можно подумать, что С++ это сплошное нельзя и вроде проканает, запустится и будет работать. Безусловно, можно сконцентрироваться на алгоритме и не обращать внимание на все описанные выше проблемы, но, к сожалению, большое количество CVE, которые заводят именно на приложения, написанные на С++, зашкаливает.

Разберем несколько примеров.

String Format

void check_password(const char *user) {  int ret;  //  static const char format[] = "%s wrong pass.\n";  size_t messageLength = strlen(user) + sizeof(msg_format);  char *data = (char *)malloc(messageLength); // <- так же не очень безопасный вариант  if (data == NULL) {    //Код для ошибки  }  ret = snprintf(data, messageLength, format, user);  if (ret < 0) {     //Код для ошибки  } else if (ret >= messageLength) {     //Последний шанс обработать некорректные данные  }  syslog(LOG_INFO, msg);  free(msg);}

В чем проблема? Данные, которые используются для создания строки, контролируются пользователем. Передача других спец символов (%n, %x) и использование строк больших размеров может вывести из строя приложение.

Integer overflow

Данная проблема головная боль любого ПО, которое работает с накапливаемыми данными. Какого размера переменные использовать, чтобы оптимально хранить данные и одновременно сделать запас для приложения, если если оно будет использоваться месяцами без перезапуска? В некоторых случаях ответить однозначно на этот вопрос нельзя, поэтому программист, ориентируясь на собственный опыт, волевым решением пишет uint16_t. Но данных оказывается больше 65,535 и тут случается чудо значение переменной становится равно нулю и отсчет идёт заново. Пример кода:

...user->nameLength = getUserNameLength(&user->name) ;user->newDbCellLen = malloc(user->nameLength * sizeof(uint8_t))...

Одна строка и один выстрел в голову всему приложению. Теперь пользователь может спокойно выделять столько памяти, сколько ему нужно. При этом приложение продолжит какое-то время работать. Исправить можно с помощью простой функции:

...int16_t checkLen(uint16_t firstNumber, uint16_t secondNumber){    uint16_t resultLength;    if (UINT_MAX - firstNumber < secondNumber)     {        //ошибка        return -1;    }    else    {        resultLength = firstNumber + secondNumber;    }    return resultLength;}

Преобразование типов

Если вас не пугают сложности и вы все-таки хотите развиваться как программист С++, то скорее всего к вам в руки попадет код, который разрабатывался Когда динозавры под стол пешком ходили и в нем будет много кода, аналогичному приведенному ниже:

...unsigned int number = (unsigned int)ptr;number = (number & 0x7fffff) | (flag << 23);ptr = (char *)number;...

Что плохого в таком коде? Не понятно, что хотел описать программист с точки зрения алгоритма. Да, это использование битовых масок, но что конкретно они собирают?
В добавок к этой проблеме может возникнуть следующее: тип данных, который задумывался программистом, может не совпасть с тем, что получится после проведения всех операций.

Выводы

Как видно из примеров в статье, С++ это язык, в котором нужно внимательно относиться к кажущимся мелочам, начиная от форматов данных и заканчивая типами переменных. Где брать примеры? Что делать с уязвимостями? К сожалению, универсального ответа нет, но можно постоянно работать и накапливать знания о языке и его особенностях. Начать можно здесь или здесь. Так же нужно использовать плагины и приложения, которые позволяют анализировать код на этапе сборки. Можно в этом случае ориентироваться на продукты вроде этого или этого.


Узнать подробнее о курсе "C++ Developer. Professional".

Смотреть открытый вебинар на тему Области видимости и невидимости.

Подробнее..

Перевод Почему вы можете обойтись без Babel

01.03.2021 20:22:05 | Автор: admin

Для будущих студентов курса "JavaScript Developer. Basic" подготовили перевод материала.

Также приглашаем всех желающих на открытый вебинар
Код как проект настройка окружения для разработки на JavaScript. На вебинаре участники вместе с экспертом рассмотрят основные инструменты, которые помогают делать код лучше и чище prettier, eslint, husky.


В наше время фронтенд-разработчики все еще тратят много времени на чрезмерную подготовку и настройку программного обеспечения. Babel воспринимается некоторыми как вынужденная необходимость, но я намерен показать вам, что это не так.

Ознакомившись с этой статьей, вы поймете:

  • как выяснить, какие браузеры действительно требуют дополнительной поддержки в каждом конкретном случае.

  • как использовать редактор Visual Studio Code, чтобы обойтись без Babel.

  • существует другая альтернатива программного обеспечения, чтобы сделать ту же работу быстрее.

Что такое Babel и какую проблему он решает?

Babel это компилятор, который преобразует ваш современный JavaScript для запуска в старых браузерах. Он также может выполнять и другие задачи, такие как преобразование синтаксиса JSX, но это не единственный инструмент для этого.

По мере развития браузеров добавляются новые функции API и ECMAScript. Различные браузеры развиваются с разной скоростью и расставляют акценты в качестве приоритетных для разных задач. Это ставит нас перед непростым выбором: как мы можем их все поддерживать и при этом использовать современные функции? Некоторые из них будут несовместимы.

Обычное решение заключается в том, чтобы использовать новейшие возможности и трансформировать их в более старый код, который будет понятен браузеру. Транспилирование (Transpiling сочетание двух слов: transforming преобразование и compiling компиляция) описывает специализированный тип компиляции. Она имеет различные значения в разных контекстах. В нашем случае также существуют две отдельные составляющие для переноса (транспилирования).

Разница между транспилированием (transpiling) и полифилингом (polyfilling)

Транспилирование (Transpiling) это процесс преобразования синтаксиса нового языка, который старые браузеры не могут понять, в старый синтаксис, который они распознают.

Приведем пример переноса оператора let:

// the new syntax `let` was added in ECMAScript 2015 aka ES6let x = 11;// `let` transpiles to the old syntax `var` if your transpiler target was ES5var x = 11;

Полифилинг (Polyfilling) это процесс добавления недостающих методов, свойств или API к старым браузерам путем предоставления собственной версии недостающего родного кода.

Это можно рассматривать как дополнение недостающих элементов. Например, вот полифил (polyfill) для isNaN:

// check if the method `isNaN` exists on the standard built-in `Number` objectif (!Number.isNaN) {  // if not we add our own version of the native method newer browsers provide  Number.isNaN = function isNaN(x) {    return x !== x;  };}

Наилучшим способом для получения полифилов является использование core-js.

Транспилирование и полифилинг иногда невозможны и могут значительно увеличить объем кода, лучше всего их вообще избегать, если это возможно. Поэтому альтернатива, которую мы будем рассматривать в первую очередь, является наиболее оптимальным решением.

Альтернатива 1: не поддерживать древние браузеры

Если бы пользователи просто обновили свои браузеры, мы могли бы избежать хлопот, связанных с транспилированием, и они могли бы наслаждаться усовершенствованными функциями и производительностью нового браузера. К сожалению, все не так просто.

Главный виновник крупные корпорации, которым приходится поддерживать старое программное обеспечение. Классическим примером является Internet Explorer, который с самого начала был помехой для веб-разработок.

Тем не менее, за последние годы многое изменилось в лучшую сторону. Теперь большинство существующих браузеров актуальны, то есть они постоянно обновляются. Microsoft продвигает свой современный браузер Edge, который, что удобно, использует тот же движок V8, как и Chrome, что означает сокращение количества поддерживаемых движков на один.

Чтобы определить, нужно ли поддерживать определенный браузер, задайте себе следующие вопросы.

1. Какие браузеры в настоящее время используют Ваши клиенты?

Если у вас уже есть веб-сайт или приложение, которое обслуживает одну и ту же клиентскую базу, вы можете получить эту информацию из программы аналитики. Ниже приведены некоторые последние статистические данные с британского сайта звуковой инженерии, которым я управляю. Если бы это было JavaScript-приложение с теми же клиентами из той же категории, я бы предположил, что они будут использовать те же браузеры.

Если у вас не установлено аналитическое программное обеспечение, вы не будете знать, какие браузеры вам нужно поддерживать. Вы должны будете сделать обоснованное предположение. Если у вас есть корпоративные клиенты, гораздо больше шансов, что вам понадобится поддержка IE11 (Internet Explorer 11), чем если бы вы занимались маркетингом для фанатов web-literate (грамотное программирование) технологий.

Когда вы поддерживаете браузер, то принимаете на себя обязательства. Вы действительно хотите иметь лишние тесты в каждом выпуске, дополнительные инструменты для настройки и больше различных функций, чтобы это обслуживать?

Должна быть ясная финансовая причина, чтобы пойти на все эти хлопоты. Будет ли потеря тех клиентов, которые не могут получить доступ к вашему сайту или приложению, стоить дороже, чем поддержка браузера?

2. Какие современные функции браузера вы хотите использовать?

Использование современных функций языка и API (Application Programming Interfaces) браузера делает написание кода проще, быстрее и интереснее. Это также делает ваш код более удобным в обслуживании.

Если вам нравиться писать ES5 (ECMAScript) и использовать XMLHttpRequest(), тогда определенно не нужен Babel, но может потребоваться какая-нибудь специальная процедура.

3. Какие современные функции браузера поддерживают браузеры ваших клиентов?

Эти данные доступны через Can I use (могу ли я использовать), но это напрасная трата времени на их поиск вручную. Теперь, когда вы знаете названия браузеров, которые вы хотите поддерживать, поиск совместимых функций может быть автоматизирован с помощью удивительного приложения Browserlist (подробнее об этом в следующем разделе).

Альтернатива 2: Используйте eslint-plugin-compat

Вы можете вообще избежать всего процесса переноса (transpiling) и вместо этого позволить вашему редактору кода предупредить вас, если вы используете какие-либо функции, которые слишком сложны для браузеров ваших клиентов. Это самый простой вариант, потому что он:

  • исключает любую зависимость от транспилиров (transpilers). Возвращает вам практический контроль над рабочим кодом.

  • если имеется современная функция, без которой вы не можете жить, то ее можно использовать применив полифил (polyfill).

В противном случае, вы можете просто использовать старый синтаксис, когда это необходимо.

Создать тест

Прежде чем мы сможем разобраться в плюсах и минусах, надо убедиться, что наши альтернативы Babel могут выполнять ту же самую основную работу. Давайте сделаем небольшой тест.

Ниже приведен современный код, который должна поддерживать наша целевая среда после переноса (transpiled).

После переноса (transportation) для каждой функции есть console.assert (метод записи сообщений на консоли для пользователя), чтобы убедиться, что она работает, как положено. В случае eslint-plugin-compat вместо этого проверим, что несовместимый код помечен в linting (Linting это процесс, выполняемый программой linter, которая анализирует исходный код на определенном языке программирования и отмечает потенциальные проблемы, такие как синтаксические ошибки, отклонения от предписанного стиля кодирования или использование конструкций, о которых известно, что они небезопасны).

test.js

// test nullish coalescing - return right side when left side null or undefinedconst x = null ?? "default string";console.assert(x === "default string");const y = 0 ?? 42;console.assert(y === 0);// test optional chaining - return undefined on non existent property or methodconst adventurer = {  name: "Alice",  cat: {    name: "Dinah",  },};const dogName = adventurer.dog?.name;console.assert(dogName === undefined);console.assert(adventurer.someNonExistentMethod?.() === undefined);// use browser API fetch, to check lintingfetch("https://jsonplaceholder.typicode.com/todos/1")  .then((response) => response.json())  .then((json) => console.log(json));

Использование eslint env свойства с помощью eslint-plugin-compat

Нам нужен обходной путь для объединения функций языка и API браузера.

Вы можете использовать eslint (Eslint это утилита, проверяющая стандарты кодирования на JavaScript) для проверки синтаксиса языка. Для этого измените свойство env наes2020.

Для проверки совместимости API браузера используйте eslint-plugin-compat. Он использует ту же самую конфигурацию Browserlist и остальные инструменты, что и Babel.

Полную инструкцию можно найти в eslint-plugin-compat repo. Мы воспользуемся browserlist defaults как предустановками по умолчанию. Замените их по своему выбору, основанному на аналитике.

Что такое browserlist?

Browserlistавтоматически выбирает список браузеров, основываясь на различных критериях, которые Вы ему предоставляете.

Посмотрите список браузеров, поддерживаемых defaults для browserlist.

defaults использует быстрый доступ к таким версиям браузеров:

  • > 0,5 процента (версии браузеров, выбранные по глобальной статистике использования)

  • Последние две версии (каждого "живого (not dead)" браузера)

  • Firefox ESR (Extended Support Release)

  • Живые (not dead) (браузеры без официальной поддержки и обновлений в течение 24 месяцев).

Перейдите на GitHub, чтобы получить полный список запросов, доступных для выбора поддерживаемых браузеров.

Настройка eslint-plugin-compat для Visual Studio Code

Добавьте следующие пакеты в свой проект.

npm install --save-dev eslint eslint-plugin-compat

Добавьте следующее в package.json.

"browserslist": [    "defaults"  ]

Создайте следующий файл .eslintrc.json или добавьте эти настройки к существующим.

{  "extends": ["plugin:compat/recommended"],  "env": {    "browser": true,    "es2020": true  }}

Убедитесь, что у вас установлено расширение VS Code ESLint.

Теперь любой API браузера, несовместимый с конфигурацией browserlistв вашем package.json, отображается как ошибка linting. Вы можете отдельно контролировать, какую версию ECMAScript вы хотите поддержать, используя свойство env в файле .eslintrc.json.

Было бы неплохо, если бы eslint-plugin-compat автоматически добавил и возможности языка, но на данный момент это является нерешённой задачей.

IE 11 с выбранной настройкой

наш API fetch() помечен.

Поменяйте объект env на es6.

Вы сразу же увидите ошибку при попытке использовать nullish coalescing, который был запущен в составе Es2020.

Альтернатива 3: Используйте другое программное обеспечение для замены Babel

Прежде чем рассматривать альтернативы, давайте быстро рассмотрим, как использовать Babel.

Использование Babel для транспилирования (transpile) и полифилинга (polyfill)

Сначала создайте директорию мини-проекта и установите нужные нам взаимосвязи.

mkdir babel-testcd babel-testnpm init -ymkdir src distnpm install --save-dev @babel/core @babel/cli @babel/preset-envnpm install --save @babel/polyfill

Добавьте следующее в свой package.json.

"browserslist": "defaults",

Запишите файл test.js вsrc, а затем выполните следующую команду.

npx babel src --out-dir dist --presets=@babel/env

Наконец, запустите файл, чтобы проверить, что тесты все еще работают.

node dist/test.js

Ошибок ввода не должно быть, но будет сказано, что fetch is not defined, так как в Node.js нет метода fetch().

Вот результирующий транспилированный (transpiled) код. Обратите внимание на весь лишний мусор и хлам.

"use strict";var _ref, _, _adventurer$dog, _adventurer$someNonEx;// test nullish coalescing - return right side when left side null or undefinedvar x = (_ref = null) !== null && _ref !== void 0 ? _ref : "default string";console.assert(x === "default string");var y = (_ = 0) !== null && _ !== void 0 ? _ : 42;console.assert(y === 0); // test optional chaining - return undefined on non existent property or methodvar adventurer = {  name: "Alice",  cat: {    name: "Dinah",  },};var dogName =  (_adventurer$dog = adventurer.dog) === null || _adventurer$dog === void 0    ? void 0    : _adventurer$dog.name;console.assert(dogName === undefined);console.assert(  ((_adventurer$someNonEx = adventurer.someNonExistentMethod) === null ||  _adventurer$someNonEx === void 0    ? void 0    : _adventurer$someNonEx.call(adventurer)) === undefined,); // use browser API fetch, to check lintingfetch("https://jsonplaceholder.typicode.com/todos/1")  .then(function (response) {    return response.json();  })  .then(function (json) {    return console.log(json);  });

Преимущества и недостатки использования Babel

Преимущества:

  • Эта базовая установка была относительно несложной.

  • У Babel есть большое сообщество для поддержки и постоянных обновлений с 36.8k GitHub звездами на момент написания статьи.

Недостатки:

  • Медленное время компиляции

  • Множество зависимостей (dependencies), даже если они являются зависимостями (dev-dependencies). (установлено 269 пакетов)

  • 39М использованного дискового пространства, как сообщает du -sh

  • 5728 установленных файлов, о чем сообщает find . -тип f | wc -l

Использование swc для транспилирования (transpile) и полифилинга (polyfill)


Swc новый конкурент Babel. Он написан на языке программирования Rust и в 20 раз быстрее. Это может быть очень важно, если вы долго ждете, чтобы построить свой проект.

Чтобы все устроить:

mkdir swc-testcd swc-testnpm init -ymkdir src distnpm install --save-dev @swc/cli @swc/core browserslist

Добавьте следующее в свой package.json.

"browserslist": "defaults",

Запишите конфигурационный файл .swcrc в корневую директорию проекта.

{  "env": {    "coreJs": 3  },  "jsc": {    "parser": {      "syntax": "ecmascript"    }  }}

Запишите ваш тестовый файл в src, затем выполните следующую команду для переноса (transpile).

npx swc src -d dist

Запустите полученный файл, чтобы проверить, что тесты все еще работают.

node dist/test.js

В итоге swc-transpiled (транспилированный) файл, выглядит вот так:

var ref, ref1;var ref2;// test nullish coalescing - return right side when left side null or undefinedvar x = (ref2 = null) !== null && ref2 !== void 0 ? ref2 : "default string";console.assert(x === "default string");var ref3;var y = (ref3 = 0) !== null && ref3 !== void 0 ? ref3 : 42;console.assert(y === 0);// test optional chaining - return undefined on non existent property or methodvar adventurer = {  name: "Alice",  cat: {    name: "Dinah",  },};var dogName =  (ref = adventurer.dog) === null || ref === void 0 ? void 0 : ref.name;console.assert(dogName === undefined);console.assert(  ((ref1 = adventurer.someNonExistentMethod) === null || ref1 === void 0    ? void 0    : ref1.call(ref1)) === undefined,);// use browser API fetch, to check lintingfetch("https://jsonplaceholder.typicode.com/todos/1")  .then(function (response) {    return response.json();  })  .then(function (json) {    return console.log(json);  });

Преимущества и недостатки использования swc

Преимущества:

  • Гораздо меньше зависимостей (установлено 43 пакета)

Недостатки:

  • Меньшая пользовательская база и количество постоянных участников

Другие альтернативы: Google Closure Compiler и TypeScript

Я не включил Google Closure Compiler в качестве опции, потому что он, как это ни печально, сложен в использовании. Тем не менее, он может сделать хорошую работу по транспилированию (transpile) и полифилингу (polyfill). Если у вас есть свободное время, я рекомендую вам проверить его особенно если вы цените небольшой размер файла, так как встроенная функция минификации демонстрирует отличные результаты.

Вы также можете использовать TypeScript для переноса (transpile) и core-js для ручной полифил (polyfill) обработки, но это неуклюжее решение, которое может с легкостью создать больше проблем, чем решить их.

Заключение

Вам не нужно автоматически поддерживать старые браузеры. Важно сначала взглянуть на свои аналитические данные, чтобы увидеть, какие браузеры на самом деле используют ваши клиенты.

При необходимости Вы можете использовать функцию linting, чтобы убедиться в их совместимости друг с другом. Это избавит Вас от лишних хлопот, связанных с созданием специального сборочного процесса и транспиляции (transpilation).

Если вы выберете автоматический перевод, то SWC будет намного быстрее, чем Babel, и будет содержать гораздо меньше зависимостей. Также есть возможность использовать Google Closure Compiler или TypeScript, но для их настройки потребуется немного больше усилий.

LogRocket: Полная видимость ваших веб-приложений

LogRocket это передовое решение для мониторинга приложений, позволяющее воспроизводить проблемы так, как если бы они возникали в вашем собственном браузере. Вместо того, чтобы гадать, почему происходят ошибки, или спрашивать пользователей на скриншотах и дампах логов, LogRocket позволяет воспроизводить сеанс, чтобы быстро понять, что пошло не так. Он отлично работает с любым приложением, независимо от фреймворка, и имеет плагины для записи дополнительного контекста из Redux, Vuex и @ngrx/store.

В дополнение к регистрации действий и состояния Redux, LogRocket записывает журналы консольных сообщений, ошибки JavaScript, следы стеков, сетевые запросы/ответы в формате заголовок + тело, метаданные браузера и пользовательские журналы. Кроме того при помощи DOM (Document Object Model) позволяет записывать страницы HTML и CSS, воссоздавая превосходные в пиксельном отношении видео даже для самых сложных одностраничных приложений.


Узнать подробнее о курсе "JavaScript Developer. Basic".

Смотреть открытый вебинар Код как проект настройка окружения для разработки на JavaScript.

Подробнее..

Перевод Зависимости JavaScript Все, что вы когда-либо хотели знать, но боялись спросить

02.03.2021 16:05:07 | Автор: admin

Ваше подробное руководство по пяти типам зависимости

Привет, хабровчане. Для будущих учащихся на курсе "JavaScript Developer. Professional" подготовили перевод материала.

Также приглашаем всех желающих на открытый вебинар по теме
Vue 3 возможности новой версии одного из самых популярных фронтенд фреймворков. На занятии участники вместе с экспертом:
рассмотрят ключевые отличия в синтаксисе vue2 от vue3;
разберут, как работать с vue-router и VueX в новой версии фреймворка;
cоздадут проект на Vue 3 с нуля с помощью Vue-cli.


Независимо от того, являетесь ли Вы back-end разработчиком, работающим с Node.js, или front-end разработчиком, использующим Node.js только в качестве инструмента для пакетирования и комплектации, Вы наверняка наткнулись на систему зависимостей.

Но почему их 5 типов (да, это не опечатка, есть 5 типов зависимостей), и для какого случая они используются? На эти вопросы мы ответим сегодня, так что сядьте поудобнее и расслабьтесь, потому что это будет интересно.

. . .

Normal (runtime) dependencies (cтандартные (во время выполнения программы) зависимости)

Давайте начнем с простого, хорошо?

Стандартные зависимости это те, которые вы видите в списке "dependencies" в вашем файле package.json. В большинстве случаев они указывают только имя (для своего ключа) и версию (значение), а затем NPM (node package manager или любой другой менеджер пакетов) позаботится об их захвате из глобального регистра (на npmjs.org).

Однако, это еще не все. Вместо точного номера версии вашей зависимости вы можете указать:

  • Примерный вариант. Вы можете работать с обычными числовыми операторами сравнения, указывая версии больше одного определенного числа (т.е. >1.2.0 ), любой версии ниже или равной другому числу (т.е. <=1.2.0 ), а также можете обыгрывать любую из их комбинаций (т.е. >= , > , <) . Вы также можете указать эквивалентную версию с помощью оператора ~ перед номером версии (т.е. "lodash":"~1.2.2, который загрузит что угодно между 1.2.2 и 1.3.0 или, другими словами, только патчи). И мы также можем указать "совместимую" версию с другим номером, который использует semver для понимания совместимости (т.е. "lodash": "^1.2.0", которая не загрузит ничего, из того что включает в себя изменение с нарушением или отсутствием каких-либо функций).

  • URL-АДРЕС. Правильно, вы даже можете обойтись без версии и напрямую ссылаться на определенный URL, по сути загрузив этот модуль откуда-нибудь еще (например, с Github или напрямую скачав tarball-файл).

  • Локальный файл. Да, вы даже можете непосредственно обращаться к одному из ваших локальных файлов. Это очень удобно, если вы разрабатываете модуль и хотите протестировать его на проекте, прежде чем выпускать его на NPM. С помощью опции "Локальный файл" вы можете сделать ссылку на папку вашего локального модуля. Вы можете использовать как полный, так и частичный пути, при условии, что вы используете префикс-функцию с помощью file:// .

Когда ты будешь использовать стандартные зависимости?

Стандартные зависимости будут содержать все, что требуется вашему проекту для работы, и что не может быть предоставлено другим модулем.

Это может показаться простым, но если, например, ваш проект на самом деле является модулем, то вам нужно более внимательно разобраться в том, что это значит. Если ваш модуль предназначен для использования с другими проектами, такими как React или Babel, ваши зависимости не должны включать их, так как они, как предполагается, уже присутствуют, тем не менее, они все равно нужны (мы это увидим позже, где они появятся через секунду).

Так что, помните, если это абсолютно необходимо для работы вашего проекта, и вы не можете рассчитывать на то, что другая библиотека включит их, то они должны быть включены в ваш список зависимостей.

. . .

Peer dependencies (Равные зависимости)

Peer dependencies это особый вид зависимостей, в том смысле, что они предназначены для указания тех пакетов, которые требуются вашему проекту, или, вернее, с которыми он совместим. Тем не менее, вы можете не запрашивать их конкретно в своем коде.

Подождите, зависимость, которая вам не требуется? Я знаю, это звучит странно, но подумайте о плагине Babel, он нуждается в Babel в том смысле, что если его там нет, то он не сможет работать, однако, он может и не требовать Babel явно для чего-либо.

Теперь, потенциально, вы можете добавить Babel в список стандартных зависимостей, однако, при установке плагина, он также попытается установить Babel. И если вы используете, например, NPM, он установит его как локальную зависимость вашего плагина, что означает он будет дублировать уже существующую установленную Babel для хост-проекта, находящегося в папке плагина.

Когда ты будешь использовать Peer dependencies?

Вы можете прочитать больше о Peer dependencies здесь, однако, поймите, что вы будете использовать их только тогда, когда нужно указать на недостаточно прочную связь с другим проектом.

Другими словами:

  • Когда она вам нужна, но нет необходимости употреблять ее сразу и однозначно. Тогда это peer dependency.

  • Когда она вам нужна, но она уже должна быть установлена другим проектом. Тогда это peer dependency.

Примерами того, когда вы хотите использовать peerDependencies, являются:

  • Babel плагины. Ты хочешь декларировать такие же вещи, как и сам Babel, в качестве равной зависимости (peer dependency).

  • Express middleware packages (Экспресс-пакеты для промежуточной обработки): Это всего лишь один пример модуля NPM, который требует использования peer dependencies. Вы хотите определить приложение Express как зависимость, но не жесткую, в противном случае каждое промежуточное ПО (middleware) будет каждый раз инсталлировать всю структуру заново.

  • Если вы собираете Micro Frontend (Микрофронтенд), пытаясь решить, какие зависимости являются внешними (чтобы они не были связаны), а какие нет. Peer dependencies могут быть хорошим решением для этого.

  • Bit components. Если вы создаете и публикуете front-end компоненты, например, когда вы совместно используете React-компоненты на Bit (Github), вам нужно назначить react библиотеку как peer dependency. Это позволит удостовериться, что нужная библиотека доступна в хостинговом проекте без установки ее в качестве прямой зависимости от него.

Например, взгляните на этот React компонент, который я опубликовал некоторое время назад: это простая кнопка, которую можно выбрать (вы нажимаете на нее и она остается выбранной до тех пор, пока не будет нажата снова).

Снимок экрана component, как видно на Bits component hub.

Если вы установите его, вы получите полный код, который содержит файл package.json, в котором перечислены все peer dependencies:

Обратите внимание, что пакет не имеет прямой зависимости, несмотря на то, что для работы ему необходима React-библиотека.

Это также помогает сохранить размер нашего компонента как можно меньше (1KB) ничего лишнего не добавляется.

. . .

Dev Dependencies (Dev зависимости)

Имя говорит обо всем, не так ли? Я имею в виду, что так и должно быть, но когда имена действительно помогают нам, разработчикам, в нашей повседневной работе? Я точно знаю.

Зависимости процесса разработки (Development dependencies) предназначены для того, чтобы содержать только те модули, которые вы используете на этапе разработки вашего проекта.

Да, но есть и другие, например, инструменты для подшивки (linting tools), документация и тому подобное.

Дело в том, что если вы можете запустить проект без использования этого набора инструментов, тогда это dev dependency.

Принцип их работы заключается в том, что зависимости разработки устанавливаются только при запуске npm install или npm link из корневой папки вашего проекта.

Подумайте об этом, в этом случае имеет смысл установить их, так как вы разрабатываете свой собственный проект. Но если вы устанавливаете свой проект внутри другого, то этот процесс будет игнорировать ваши dev dependencies.

Когда ты будешь использовать dev dependencies?

Любая зависимость, которая не требуется для производственного процесса, скорее всего, будет считаться dev dependencies (зависимости развития).

Дело в том, что это отлично работает для всех модулей, кроме вашего хост-проекта.

Все установленные внутри хост-проекта зависимости будут пропускать dev-зависимости модулей во время инсталляции, но всякий раз, когда вы повторно запускаете npm install в хост-проекте, он будет устанавливать заново все пропущенные ранее dev-зависимости модулей.

Поэтому рассмотрим использование Dev зависимостей только для пакетов и модулей, которые будут использоваться другими проектами.

. . .

Связанные зависимости (Bundled Dependencies)

Они предназначены для тех случаев, когда вы упаковываете свой проект в один файл. Это делается с помощью команды npm pack, которая превращает вашу папку в тарбол (tarball-файл).

Теперь, если вы сделаете это и добавите имена некоторых из ваших зависимостей в массив под названием bundledDependencies (забавный факт: если вы используете bundleDependencies, это тоже будет работать), то тарбол также будет содержать эти зависимости внутри этого массива.

{...   "dependencies": {    "lodash": "1.0.2",    "request": "4.0.1"  },  "bundledDependencies": ["lodash"]...}

Посмотрите на эту часть файла package.json, с такой установкой, при запуске команды npm pack вы получите файл tarball, также содержащий пакет lodash внутри.

Это очень удобно при распространении ваших пакетов в однофайловом формате (помните, что вы также можете указать их URL или локальный путь как стандартную зависимость).

. . .

Дополнительные зависимости (Optional dependencies)

Наконец, этот тип зависимостей такой же, как и обычные, они ведут себя и могут быть настроены точно таким же образом. Единственная разница? NPM не выйдет с ошибкой, в случае, если не сможет установить их.

Обычно, когда вы запускаете npm install и процесс не может установить обычную зависимость по какой-либо причине (будь то из-за сбоя в сети, не может найти файл, версию или что-нибудь ещё), то он выдаст ошибку и отменит процесс установки.

Дополнительные зависимости вместо этого позволят NPM продолжать работу. Конечно, вы отвечаете за написание кода, который способен справиться с этими отсутствующими зависимостями, например:

let foo = null;try {  foo = require("foo-dep");} catch (e) {  foo = require("./local-polyfill")}//... use foo from here on out

Когда вы будете использовать дополнительные зависимости (optional dependencies)?

Очевидный пример использования этого типа зависимостей когда вы используете ненадежный источник. Я спрашиваю, зачем это делать? Хороший вопрос. Понятия не имею, но вы определенно можете это сделать, единственная проблема в том, что вам нужно убедиться, что ваш код может с этим справиться, если отсутствуют зависимости.

Но другой интересный вариант использования, это установка действительно необязательных зависимостей (optional dependencies). Я имею в виду, что иногда у вас могут быть специфические для системы зависимости, для таких вещей, как совместимость с CI(Continuous Integration)-платформой. В таких сценариях, когда используете платформу, вы захотите установить эти зависимости, в другом случае, проигнорируете.

Для таких ситуаций можно использовать обычную npm install, когда вы используете полный набор зависимостей, а затем использовать npm install --no-optional, когда вы хотите избежать их. Таким образом, вы пропустите эти опции и сосредоточитесь только на обязательных зависимостях.

. . .

Каждый, кто когда-либо пользовался NPM в тот или иной момент, слышал о стандартных и dev-зависимостях. Остальные 3, однако, можно считать менее популярными собратьями.

Хотя некоторые из них действительно сосредоточены на очень специфических сценариях, они могут быть очень удобны, когда с ними сталкиваешься.

Знали ли вы, что у вас так много вариантов? Оставьте комментарий ниже, если вы использовали некоторые из менее распространенных и расскажите нам, как вы их использовали!


Узнать подробнее о курсе "JavaScript Developer. Professional".

Смотреть открытый вебинар по теме Vue 3 возможности новой версии одного из самых популярных фронтенд фреймворков.

Подробнее..

Перевод Изучение методов кэширования в React

04.03.2021 20:15:20 | Автор: admin

Как использовать memoization, contexts, useMemo, useState, и useEffect

Для будущих учащихся на курсе "React.js Developer" подготовили перевод материала. Также приглашаем всех желающих на открытый вебинар ReactJS: быстрый старт. Сильные и слабые стороны.


То, что мы создадим сегодня! Фотография - автора статьиТо, что мы создадим сегодня! Фотография - автора статьи

Сбор данных в React это одно. Хранение и кэширование этих данных это другая история. Возможности кажутся бесконечными, а различия часто тонкие, что делает выбор правильной техники иногда немного сложным.

Сегодня мы исследуем различные методики и рассмотрим все их детали и тонкости. Следует ли использовать useMemo или memoization? Должен ли я хранить данные с помощью useState и context? Когда мы закончим, вы должны быть в состоянии сделать осознанный выбор в отношении кэширования данных. Вы узнаете обо всех тонкостях.

И много анимированных GIF-файлов. Что еще вы можете желать?

Давайте начнем!

Наши данные

Перед тем, как нам углубиться в код, мы можем бегло взглянуть на данные, которые будем извлекать из (большинства) наших компонентов. Файл, который действует как наш API, выглядит следующим образом:

export default function handler(req, res) {  setTimeout(    () =>      res.status(200).json({        randomNumber: Math.round(Math.random() * 10000),        people: [          { name: "John Doe" },          { name: "Olive Yew" },          { name: "Allie Grater" },        ],      }),    750  );}

Этот код выполняется, когда мы делаем запрос к пути /api/people в нашем проекте. Как видите, мы вернули объект с двумя свойствами:

  • randomNumber: случайное число в диапазоне 0-10000.

  • people: статический массив с тремя вымышленными именами.

Свойство randomNumber поможет нам визуализировать, отображаем ли мы кэшированные данные во фронтенде или нет. Просто продолжайте читать. Скоро это будет понятно.

Обратите внимание, что мы симулируем небольшую задержку сети с помощью setTimeout.

Компонент People

Когда мы отображаем данные из нашего API, мы передаем их компоненту под названием PeopleRenderer. Это выглядит так:

Учитывая все вышесказанное, давайте посмотрим на первое решение ниже.

. . .

useEffect

Внутри наших компонентов мы могли бы использовать хук-эффект useEffect Hook для получения данных. Затем мы можем хранить их локально (внутри компонента), используя useState :

import { useEffect, useState } from "react";import PeopleRenderer from "../PeopleRenderer";export default function PeopleUseEffect() {  const [json, setJson] = useState(null);  useEffect(() => {    fetch("/api/people")      .then((response) => response.json())      .then((data) => setJson(data));  }, []);  return <PeopleRenderer json={json} />;}

При передаче пустого массива в качестве второго параметра (см. строку 11), useEffect Hook (хук-эффект) будет выполнен, когда наш компонент будет установлен в DOM (Document Object Model) и только после этого. Он не будет выполняться снова, когда наш компонент будет перезагружен. Это называется "выполнить один раз" Hook (хук).

Ограничение использования useEffect в этом случае заключается в том, что когда мы имеем несколько экземпляров компонента в нашем DOM, все они будут получать данные по отдельности (когда они установлены):

В этом способе нет ничего плохого. Иногда, это именно то, что мы хотим. Но в других случаях нам может понадобиться один раз получить данные и повторно использовать их во всех других случаях путем кэширования. Мы можем использовать несколько способов для достижения этого.

. . .

Memoization (Мемоизация)

Memoization это причудливое слово для очень простой техники. Это означает, что вы создаете функцию, и каждый раз, когда она вызывается заново, она сохраняет результаты выполнения в кэше для предотвращения повторных вычислений.

При первом вызове этой memoized функции результаты рассчитываются (или извлекаются, что бы Вы ни делали внутри тела функции). Перед возвращением результатов вы храните их в кэше под ключом, который создается с входными параметрами:

const MyMemoizedFunction = (age) => {  if(cache.hasKey(age)) {    return cache.get(age);  }  const value = `You are ${age} years old!`;  cache.set(age, value);  return value;}

Создание такого кода шаблона может быстро стать громоздким, поэтому такие популярные библиотеки, как Lodash и Underscore, предоставляют утилиты, которые можно использовать для легкого создания memoized функций:

import memoize from "lodash.memoize";const MyFunction = (age) => {  return `You are ${age} years old!`;}const MyMemoizedFunction = memoize(MyFunction);

Использование memoization для получения данных

Мы можем использовать эту технологию при получении данных. Создаем функцию getData, которая возвращает Promise, доступный по окончании запроса на получение данных. Мемоизуем (memoize) объект Promise:

import memoize from "lodash.memoize";const getData = () =>  new Promise((resolve) => {    fetch("http://localhost:3000/api/people")      .then((response) => response.json())      .then((data) => resolve(data));  });export default memoize(getData);

Обратите внимание, что в этом примере мы не работали с ошибками. Это заслуживает отдельной статьи, особенно когда мы используем мемоизацию (memoization) (также будет мемоизован (memoized) отклонённый Promise , что может быть проблематично).

Теперь мы можем заменить нашuseEffect Hook на другой, который выглядит так:

import { useEffect, useState } from "react";import getData from "./getData";import PeopleRenderer from "../PeopleRenderer";export default function PeopleMemoize() {  const [json, setJson] = useState(null);  useEffect(() => {    getData().then((data) => setJson(data));  }, []);  return <PeopleRenderer json={json} />;}

Так как результат getData мемоизуется (memoized), все наши компоненты получат одни и те же данные, когда они будут смонтированы:

Анимация: В наших компонентах используется один и тот же memoized Promise.Анимация: В наших компонентах используется один и тот же memoized Promise.

Стоит также отметить, что данные уже предварительно собраны, когда мы открываем страницу memoize.tsx (перед тем, как мы смонтируем первую версию нашего компонента). Это потому, что мы определили нашу функцию getData в отдельном файле, который включен в верхнюю часть нашей страницы, и Promise создается при загрузке этого файла.

Мы также можем аннулировать, признать недействительным (пустым) кэш, назначив совершенно новый Cache в качестве свойства cache нашей мемоизованной (memoized) функции:

getData.cache = new memoize.Cache();

Как вариант, вы можете очистить существующий кэш (с помощью функции Map):

getData.cache.clear();

Но это специфичная функциональность для Lodash. Другие библиотеки требуют иных решений. Здесь вы видите аннулирование кэша в действии на примере:

Анимация: Сброс кэша memoized функции getData.Анимация: Сброс кэша memoized функции getData.

. . .

React Context (React Контекст)

Еще одним популярным и широко обсуждаемым (но часто непонятным) инструментом является React Context. И на заметку, еще раз, он не заменяет такие инструменты, как Redux. Это не инструмент управления состоянием.

Mark Erikson ведет тяжелую битву в Интернете и продолжает объяснять, почему это так. Настоятельно рекомендую прочитать его последнюю статью на эту тему.

И если вам это действительно интересно, прочитайте и мою статью по этой теме:

Так что же такое Context (Контекст)? Это механизм для внесения данных в ваше дерево компонентов. Если у вас есть какие-то данные, вы можете хранить их, например, с помощью useState Hook внутри компонента высоко в иерархии компонентов. Затем вы можете с помощью Context Provider внедрить данные в дерево, после чего вы сможете читать (использовать) данные в любом из компонентов внизу.

Это легче понять на примере. Во-первых, создайте новый контекст:

import { createContext } from "react";export const PeopleContext = createContext(null);

Затем мы заворачиваем (wrap) тот компонент, который отображает (renders) ваши компоненты People, с помощью Context Provider:

export default function Context() {  const [json, setJson] = useState(null);  useEffect(() => {    fetch("/api/people")      .then((response) => response.json())      .then((data) => setJson(data));  }, []);  return (    <PeopleContext.Provider value={{ json }}>        ...    </PeopleContext.Provider>  );}

На 12-ой строке мы можем сделать все, что захотим. В какой-то момент, далее вниз по дереву, мы отобразим наш компонент (компоненты) People:

import { useContext } from "react";import PeopleRenderer from "../PeopleRenderer";import { PeopleContext } from "./context";export default function PeopleWithContext() {  const { json } = useContext(PeopleContext);  return <PeopleRenderer json={json} />;}

Мы можем использовать значение от Provider с помощью useContext Hook. Результат выглядит следующим образом:

Анимация: Использование данных из Context (Контекста).Анимация: Использование данных из Context (Контекста).

Обратите внимание на одну важную разницу! В конце анимации выше мы нажимаем кнопку "Set new seed". При этом данные, которые хранятся в нашем Context Provider, будут заново извлечены. После этого (через 750 мс) вновь полученные данные становятся новым значением нашего Provider, а наши компоненты People перезагружаются. Как видите, все они используют одни и те же данные.

Это большое отличие от примера мемоизации (memoization), который мы рассмотрели выше. В том случае каждый компонент хранил свою копию мемоизуемых (memoized) данных с помощью useState. А в этом случае, используя и потребляя контекст, они не хранят копии, а только используют ссылки на один и тот же объект. Поэтому при обновлении значения в нашем Provider все компоненты обновляются одними и теми же данными.

. . .

useMemo

И последнее, но не менее важное, это беглый взгляд на useMemo. Этот Hook отличается от других методов, которые мы уже рассматривали, в том смысле, что это только форма кэширования на локальном уровне: внутри одного элемента компонента. Вы не можете использовать useMemo для обмена данными между несколькими компонентами по крайней мере, без таких обходных путей, как пробрасывание (prop-drilling) или внедрение зависимостей ((dependency injection) (например, React Context)).

useMemo является инструментом оптимизации. Вы можете использовать его для предотвращения пересчета значения при каждом повторном использовании вашего компонента. Документация описывает это лучше, чем могу я, но давайте рассмотрим пример:

export default function Memo() {  const getRnd = () => Math.round(Math.random() * 10000);  const [age, setAge] = useState(24);  const [randomNumber, setRandomNumber] = useState(getRnd());  const pow = useMemo(() => Math.pow(age, 2) + getRnd(), [age]);  return (    ...  );}
  • getRnd (строка 2): функция, возвращающая случайное число в диапазоне 0-10000.

  • age (строка 4): сохранение числа, отображающего возраст с useState.

  • randomNumber (строка 5): сохранение случайного числа с useState.

И, наконец, мы используем useMemo на 7-й строке. Мы мемоизуем (memoize) результат вызова функции и сохраняем его в виде переменной, называемой pow. Наша функция возвращает число, которое представляет собой сумму age, увеличенную до двух, плюс случайное число. Она имеет зависимость от переменной age, поэтому мы передаем ее в качестве зависимого аргумента вызова useMemo.

Функция выполняется только при изменении значения возраста. Если наш компонент повторно будет вызван (re-rendered) и значение age не изменится, то useMemo просто вернёт мемоизованный (memoized) результат.

В данном примере вычисление pow не очень сложное, но вы можете представить себе преимущества этого, учитывая, что наша функция становится более тяжелой и нам приходится часто визуализировать (re-rendered) наш компонент.

Последние две анимации покажут, что происходит. Во-первых, мы обновляем компонент randomNumber и не затрагиваем значение age. Таким образом, мы видим useMemo в действии (значение pow не меняется при ререндеринге (re-rendered) компонента. Каждый раз, когда мы нажимаем на кнопку randomNumer, наш компонент возвращается (re-rendered) к исходному значению:

Анимация: Много обращений (re-renders), но значение pow мемоизуется (memoized) с useMemo.Анимация: Много обращений (re-renders), но значение pow мемоизуется (memoized) с useMemo.

Однако, если мы изменяем значение age, pow получает повторный отклик (re-rendered), потому что наш useMemo вызов имеет зависимость от значения age:

Анимация: Наше мемоизованное (memoized) значение обновляется при обновлении зависимости.Анимация: Наше мемоизованное (memoized) значение обновляется при обновлении зависимости.

. . .

Заключение

Существует множество методик и утилит для кэширования данных в JavaScript. Эта статья дает только поверхностные знания, но я надеюсь, что она содержит некоторые сведения, которые вы захватите с собой на пути своего развития.

Весь программный код, использованный в этой статье, можно найти у меня в архиве на GitLab.

Спасибо, что уделили время!


Узнать подробнее о курсе "React.js Developer".

Смотреть открытый вебинар ReactJS: быстрый старт. Сильные и слабые стороны.

Подробнее..

Перевод SQLite с использованием Go и Python

10.02.2021 20:14:57 | Автор: admin

Для будущих студентов курса "Golang Developer. Professional" и всех интересующихся подготовили перевод интересного материала.

Также приглашаем на
открытый вебинар по теме Go-каналы снаружи и внутри. Каналы в Go это одна из основ и особенностей языка. Понимание того, как они устроены ключ к правильному и оптимальному использованию. Присоединяйтесь, чтобы обсудить тему.


Введение

В основном я предпочитаю использовать реляционные базы данных (SQL), поскольку они предоставляют несколько возможностей, которые весьма полезны при работе с данными. SQLite - отличный выбор, так как база данных там представляет собой единый файл, что упрощает обмен данными. Несмотря на то, что это единый файл, SQLite может обрабатывать до 281 терабайта данных. SQLite также поставляется с клиентом командной строки sqlite3, который отлично подходит для быстрого прототипирования.

Примечание: В других базах данных есть транзакции, языки запросов и схемы. Однако базы данных на основе SQL имеют тенденцию быть более развитыми испытанными временем, а сам SQL стандартизирован.

Определения

Начнем с нескольких определений, которые помогут нам внести ясность.

SQLite: это встроенная база данных SQL. SQLite небольшая, быстрая и самая часто используемая база данных. Это также, безусловно, мой излюбленный способ обмена большими объемами данных между двумя программами.

Транзакции: вы вводите данные в базу данных SQL внутри транзакции. Это означает, что либо поступают сразу все данные, либо не поступают никакие. Транзакции на порядки упрощают код повторного выполнения операций в конвейерах данных.

Схема: данные в реляционных базах данных имеют схему, что означает, что их легче проверить на достоверность.

SQL: Structured Query Language (язык структурированных запросов) это язык для выборки и изменения данных. Вам не нужно изобретать еще один способ выборки интересующих вас частей данных. SQL это устоявшийся формат, вокруг которого накоплено много знаний и инструментов.

Проект

Мы напишем HTTP-сервер на Go, который будет получать уведомления о сделках (trades) и хранить их в базе данных SQLite. Затем мы напишем программу на Python, которая будет обрабатывать эти данные.

В Go мы будем использовать github.com/mattn/go-sqlite3, который является оболочкой для библиотеки C SQLite.

Примечание: Поскольку go-sqlite использует cgo, изначальное время сборки будет больше обычного. Использование cgo означает, что результирующий исполняемый файл зависит от некоторых разделяемых библиотек из ОС, что немного усложняет распространение.

В Python мы будем использовать встроенный модуль sqlite3 и функцию read_sql Pandas, чтобы загружать данные. Исходный код из этой статьи доступен по ссылке.

Код на Go

Код, который я собираюсь вам показать, можно найти в файле trades.go.

Листинг 1: Структура Trade

37 // Trade - это сделка о покупке/продаже по символу ценной бумаги.38 type Trade struct {39     Time   time.Time40     Symbol string41     Price  float6442     IsBuy  bool43 }

В Листинге 1 показана структура данных Trade. У нее есть поле Time, отражающее время сделки, поле Symbol, хранящее биржевое сокращение (символ акции, например, AAPL), поле Price, содержащее цену, и логический флаг, который сообщает, это сделка покупки или продажи.

Листинг 2: Схема базы данных

24     schemaSQL = `25 CREATE TABLE IF NOT EXISTS trades (26     time TIMESTAMP,27     symbol VARCHAR(32),28     price FLOAT,29     buy BOOLEAN30 );31 32 CREATE INDEX IF NOT EXISTS trades_time ON trades(time);33 CREATE INDEX IF NOT EXISTS trades_symbol ON trades(symbol);34 `

В Листинге 2 объявляется схема базы данных, соответствующая структуре Trade. В строке 25 мы создаем таблицу под названием trades. В строках 26-29 мы определяем столбцы таблицы, которые соответствуют полям структуры Trade. В строках 32-33 мы создаем индексы для таблицы, чтобы обеспечить быстрое запрашивание по time и symbol.

Листинг 3: Внесение записи

16     insertSQL = `17 INSERT INTO trades (18     time, symbol, price, buy19 ) VALUES (20     ?, ?, ?, ?21 )22 `

Листинг 3 определяет SQL-запрос для внесения записи в базу данных. В строке 20 мы используем символ-заполнитель ? для параметров этого запроса. Никогда не используйте fmt.Sprintf для создания SQL-запроса вы рискуете создать SQL-инъекцию.

Вставка записей по одной за раз медленный процесс. Мы собираемся хранить записи в буфере, и, когда он достаточно заполнится, внесем все записи из буфера в базу данных. Преимущество этого подхода заключается в скорости, но он сопряжен с риском потери данных в случае сбоя на сервере.

Листинг 4: DB

45 // DB - это база данных биржевых торгов.46 type DB struct {47     sql    *sql.DB48     stmt   *sql.Stmt49     buffer []Trade50 }

В Листинге 4 описана структура DB. В строке 47 мы устанавливаем соединение с базой данных. В строке 48 мы храним подготовленный (предварительно скомпилированный) оператор для вставки, а в строке 49 находится наш буфер ожидающих транзакций в памяти.

Листинг 5: NewDB

53 // NewDB создает Trades для управления торговлей акциями в базе данных SQLite. Этот API не является потокобезопасным.54 func NewDB(dbFile string) (*DB, error) {55     sqlDB, err := sql.Open("sqlite3", dbFile)56     if err != nil {57         return nil, err58     }59 60     if _, err = sqlDB.Exec(schemaSQL); err != nil {61         return nil, err62     }63 64     stmt, err := sqlDB.Prepare(insertSQL)65     if err != nil {66         return nil, err67     }68 69     db := DB{70         sql:    sqlDB,71         stmt:   stmt,72         buffer: make([]Trade, 0, 1024),73     }74     return &db, nil75 }

Листинг 5 демонстрирует создание готовой к использованию базы данных DB. В строке 55 мы подключаемся к базе данных с помощью драйвера sqlite3. В строке 60 мы применяем SQL-схему, чтобы создать таблицу trades, если она еще не существует. В строке 64 мы предварительно компилируем инструкцию InsertSQL. В строке 72 мы создаем внутренний буфер с длиной 0 и емкостью 1024.

Примечание: Чтобы не усложнять код, предоставляемый мной API DB не горутино-безопасен (в отличие от sql.DB). Если несколько горутин вызывают API одновременно, вы столкнетесь с состоянием гонки. Я оставлю это вам в качестве упражнения сделайте этот код горутино-безопасным.

Листинг 6: Add

77 // Add сохраняет сделку в буфер. Как только буфер заполняется, сделки вносятся в базу данных.79 func (db *DB) Add(trade Trade) error {80     if len(db.buffer) == cap(db.buffer) {81         return errors.New("trades buffer is full")82     }83 84     db.buffer = append(db.buffer, trade)85     if len(db.buffer) == cap(db.buffer) {86         if err := db.Flush(); err != nil {87             return fmt.Errorf("unable to flush trades: %w", err)88         }89     }90 91     return nil92 }

В Листинге 6 приведен метод Add. В строке 84 мы добавляем сделку (trade) в буфер в памяти. В строке 85 мы проверяем, заполнен ли буфер, и если да, то мы вызываем метод Flush в строке 86, который вносит записи из буфера в базу данных.

Листинг 7: Flush

94  // Flush вносит ждущие обработки сделки в базу данных.95  func (db *DB) Flush() error {96      tx, err := db.sql.Begin()97      if err != nil {98          return err99      }100 101     for _, trade := range db.buffer {102         _, err := tx.Stmt(db.stmt).Exec(trade.Time, trade.Symbol, trade.Price, trade.IsBuy)103         if err != nil {104             tx.Rollback()105             return err106         }107     }108 109     db.buffer = db.buffer[:0]110     return tx.Commit()111 }

В Листинге 7 приведен метод Flush. В строке 96 мы начинаем транзакцию. В строке 101 мы итерируем по внутреннему буферу, а в строке 102 вносим каждую сделку. Если при внесении произошла ошибка, мы выполняем rollback в строке 104. В строке 109 мы сбрасываем буфер сделок в памяти. И наконец, в строке 110 мы выполняем commit.

Листинг 8: Close

113 // Close вносит (посредством Flush) все сделки в базу данных и предотвращает любую торговлю в будущем.114 func (db *DB) Close() error {115     defer func() {116         db.stmt.Close()117         db.sql.Close()118     }()119 120     if err := db.Flush(); err != nil {121         return err122     }123 124     return nil125 }

В Листинге 8 приведен метод Close. В строке 120 мы вызываем Flush, чтобы внести все оставшиеся сделки в базу данных. В строках 116 и 117 мы закрываем (close) инструкцию и базу данных. Функции, создающие DB, должны иметь функцию defer db.Close(), чтобы убедиться, что связь с базой данных закончена.

Листинг 9: Импорты

5 // Ваши пакеты main и test требуют эти импорты, чтобы пакет sql был правильно инициализирован.6 // _ "github.com/mattn/go-sqlite3"7 8 import (9     "database/sql"10     "errors"11     "fmt"12     "time"13 )

В Листинге 9 приведен импорт для файла. В строке 5 мы импортируем database/sql, которая определяет API для работы с базами данных SQL. database/sql не содержит какого-либо конкретного драйвера базы данных.

Как говорится в комментарии к оператору импорта, чтобы использовать пакет trades, вам необходимо импортировать пакет, который реализует драйвер базы данных sqlite3 (например, github.com/mattn/go-sqlite3). Поскольку вы импортируете пакет, реализующий драйвер только для небольшого изменения регистрации протокола sqlite3, мы используем перед импортом, сообщая компилятору Go, что то, что мы не используем этот пакет в коде это нормально.

Листинг 10: Пример использования

Код этих примеров можно найти в файле tradestest.go.

66 func ExampleDB() {67     dbFile := "/tmp/db-test" + time.Now().Format(time.RFC3339)68     db, err := trades.NewDB(dbFile)69     if err != nil {70         fmt.Println("ERROR: create -", err)71         return72     }73     defer db.Close()74 75     const count = 10_00076     for i := 0; i < count; i++ {77         trade := trades.Trade{78             Time:   time.Now(),79             Symbol: "AAPL",80             Price:  rand.Float64() * 200,81             IsBuy:  i%2 == 0,82         }83         if err := db.Add(trade); err != nil {84             fmt.Println("ERROR: insert - ", err)85             return86         }87     }88 89     fmt.Printf("inserted %d records\n", count)90     // Вывод:91     // inserted 10000 records92 }

В Листинге 10 показан пример использования (в виде тестируемого примера). В строке 67 мы создаем новую базу данных, а в строке 73 мы закрываем ее с помощью оператора defer. В строке 76 мы запускаем цикл для вставки сделок, а в строке 83 мы собственно и вставляем сделку в базу данных.

Код на Python

Примеры кода на Python можно найти в файле analysis_trades.py.

Листинг 11: Импорты

02 import sqlite303 from contextlib import closing04 from datetime import datetime05 06 import pandas as pd

В Листинге 11 показаны библиотеки, которые мы используем в нашем Python-коде. В строке 2 мы импортируем встроенный модуль sqlite3, а в строке 6 библиотеку pandas.

Листинг 12: Select SQL

08 select_sql = """09 SELECT * FROM trades10 WHERE time >= ? AND time <= ?11 """

В Листинге 12 показан SQL-запрос для получения данных. В строке 10 мы выбираем все столбцы из таблицы trades. В строке 10 мы добавляем элемент WHERE для выбора временного диапазона. Как и в Go-коде мы используем заполнители аргументов ? и не пишем SQL-запросы вручную.

Листинг 13: Загрузка сделок

14 def load_trades(db_file, start_time, end_time):15     """Загружаем сделки из db_file за заданный временной диапазон."""16     conn = sqlite3.connect(db_file)17     with closing(conn) as db:18         df = pd.read_sql(select_sql, db, params=(start_time, end_time))19 20     # Мы не можем использовать здесь detect_types=sqlite3.PARSE_DECLTYPES, поскольку Go вставляет часовой пояс, а sqlite3 Python не умеет обрабатывать его.22     # Смотрите https://bugs.python.org/issue29099# См. Https://bugs.python.org/issue2909923     df["time"] = pd.to_datetime(df["time"])24     return df

В Листинге 13 показан код для загрузки сделок из заданного временного диапазона. В строке 16 мы подключаемся к базе данных. В строке 17 мы используем менеджер контекста, что-то вроде defer в Go, чтобы убедиться, что база данных закрыта. В строке 18 мы используем функцию pandas read_sql для загрузки данных из SQL-запроса в DataFrame. Python имеет API для подключения к базам данных (например, database/sql), а Pandas может использовать любой совместимый драйвер. В строке 23 мы конвертируем столбец time в Timestamp pandas. Это особенность SQLite, в котором нет встроенной поддержки TIMESTAMP типов.

Листинг 14: Средняя цена

27 def average_price(df):28     """Возвращает среднюю цену в df, сгруппированную по (stock, buy)"""29     return df.groupby(["symbol", "buy"], as_index=False)["price"].mean()

В Листинге 14 показано, как вычислить среднюю цену на symbol и buy. В строке 29 мы используем DataFrame groupby для группировки по symbol и buy. Мы используем as_index=False, чтобы получить symbol и buy в виде столбцов в итоговом фрейме данных. Затем мы берем столбец price и вычисляем среднее значение для каждой группы.

Листинг 15: Вывод

symbol,buy,priceAAPL,0,250.82925665004535AAPL,1,248.28277375538832GOOG,0,250.11537993385295GOOG,1,252.4726772487683MSFT,0,250.9214212695317MSFT,1,248.60187022941685NVDA,0,250.3844763417279NVDA,1,249.3578146208962

В Листинге 15 показан результат выполнения кода Python на тестовых данных.

Заключение

Я настоятельно рекомендую вам рассмотреть возможность использования SQLite в вашем следующем проекте. SQLite это испытанная временем стабильная платформа, способная обрабатывать огромные объемы данных. Многие языки программирования имеют драйверы для SQLite, что делает его хорошим вариантом хранения.

Я максимально упростил код, чтобы показать наиболее интересные моменты. Есть несколько мест, где вы можете попробовать улучшить его:

  • Добавить код повторного выполнения в Flush

  • Выполнить дополнительную проверку ошибок в Close

  • Сделать DB горутино-безопасным

  • Реализовать больше аналитики в Python-части

Я также рекомендую вам изучить SQL, так как очень много данных хранятся в базах данных SQL. Вам не нужно быть экспертом, знание базовых вариантов select уже даст вам новый мощный инструмент в вашем инструментарии анализа данных.

Если вы не хотите напрямую работать с SQL, есть несколько альтернатив, в мире Go есть такие пакеты, как sqlx, gorm, а в мире Python есть SQLAlchemy, который может использовать Pandas.

Приятного вам кодинга. Дайте мне знать, как вы используете SQL в целом и SQLite в частности.


Узнать подробнее о курсе "Golang Developer. Professional".

Смотреть открытый вебинар по теме Go-каналы снаружи и внутри.

Подробнее..

Перевод Что из себя представляет класс Startup и Program.cs в ASP.NET Core

15.02.2021 16:13:26 | Автор: admin

В преддверии старта курса C# ASP.NET Core разработчик подготовили традиционный перевод полезного материала.

Также приглашаем на открытый вебинар по теме
Отличия структурных шаблонов проектирования на примерах. На вебинаре участники вместе с экспертом рассмотрят три структурных шаблона проектирования: Заместитель, Адаптер и Декоратор; а также напишут несколько простых программ и проведут их рефакторинг.


Введение

Program.cs это место, с которого начинается приложение. Файл Program.cs в ASP.NET Core работает так же, как файл Program.cs в традиционном консольном приложении .NET Framework. Файл Program.cs является точкой входа в приложение и отвечает за регистрацию и заполнение Startup.cs, IISIntegration и создания хоста с помощью инстанса IWebHostBuilder, метода Main.

Global.asax больше не входит в состав приложения ASP.NET Core. В ASP.NET Core заменой файла Global.asax является файл Startup.cs.

Файл Startup.cs это также точка входа, и он будет вызываться после выполнения файла Program.cs на уровне приложения. Он обрабатывает конвейер запросов. Класс Startup запускается в момент запуска приложения.

Описание

Что такое Program.cs?

Program.cs это место, с которого начинается приложение. Файл класса Program.cs является точкой входа в наше приложение и создает инстанс IWebHost, на котором размещается веб-приложение.

public class Program {      public static void Main(string[] args) {          BuildWebHost(args).Run();      }      public static IWebHost BuildWebHost(string[] args) => WebHost.CreateDefaultBuilder(args).UseStartup < startup > ().Build();  }  

WebHost используется для создания инстансов IWebHost, IWebHostBuilder и IWebHostBuilder, которые имеют предварительно настроенные параметры. Метод CreateDefaultBuilder() создает новый инстанс WebHostBuilder.

Метод UseStartup() определяет класс Startup, который будет использоваться веб-хостом. Мы также можем указать наш собственный пользовательский класс вместо Startup.

Метод Build() возвращает экземпляр IWebHost, а Run() запускает веб-приложение до его полной остановки.

Program.cs в ASP.NET Core упрощает настройку веб-хоста.

public static IWebHostBuilder CreateDefaultBuilder(string[] args) {      var builder = new WebHostBuilder().UseKestrel().UseContentRoot(Directory.GetCurrentDirectory()).ConfigureAppConfiguration((hostingContext, config) => {          /* setup config */ }).ConfigureLogging((hostingContext, logging) => {          /* setup logging */ }).UseIISIntegration()      return builder;  }

Метод UseKestrel() является расширением, которое определяет Kestrel как внутренний веб-сервер. Kestrel это кроссплатформенный веб-сервер для ASP.NET Core с открытым исходным кодом. Приложение работает с модулем Asp.Net Core, и необходимо включить интеграцию IIS (UseIISIntegration()), которая настраивает базовый адрес и порт приложения.

Он также настраивает UseIISIntegration(), UseContentRoot(), UseEnvironment(Development), UseStartup() и другие доступные конфигурации, например Appsetting.json и Environment Variable. UseContentRoot используется для обозначения текущего пути к каталогу.

Мы также можем зарегистрировать логирование и установить минимальный loglevel, как показано ниже. Это также переопределитloglevel, настроенный в файле appsetting.json.

.ConfigureLogging(logging => { logging.SetMinimumLevel(LogLevel.Warning); }) 

Таким же образом мы можем контролировать размер тела нашего запроса и ответа, включив эту опцию файле program.cs, как показано ниже.

.ConfigureKestrel((context, options) => { options.Limits.MaxRequestBodySize = 20000000; });  

ASP.net Core является кроссплатформенным и имеет открытый исходный код, а также его можно размещать на любом сервере (а не только на IIS, внешнем веб-сервере), таком как IIS, Apache, Nginx и т. д.

Что такое файл Startup?

Обязателен ли файл startup.cs или нет? Да, startup.cs является обязательным, его можно специализировать любым модификатором доступа, например, public, private, internal. В одном приложении допускается использование нескольких классов Startup. ASP.NET Core выберет соответствующий класс в зависимости от среды.

Если существует класс Startup{EnvironmentName}, этот класс будет вызываться для этого EnvironmentName или будет выполнен файл Startup для конкретной среды в целом (Environment Specific), чтобы избежать частых изменений кода/настроек/конфигурации в зависимости от среды.

ASP.NET Core поддерживает несколько переменных среды, таких как Development, Production и Staging. Он считывает переменную среды ASPNETCORE_ENVIRONMENT при запуске приложения и сохраняет значение в интерфейсе среды хоста (into Hosting Environment interface).

Обязательно ли этот класс должен называться startup.cs? Нет, имя класса не обязательно должно быть Startup.

Мы можем определить два метода в Startup файле, например ConfigureServices и Configure, вместе с конструктором.

Пример Startup файла

public class Startup {      // Use this method to add services to the container.      public void ConfigureServices(IServiceCollection services) {          ...      }      // Use this method to configure the HTTP request pipeline.      public void Configure(IApplicationBuilder app) {          ...      }  }  

Метод ConfigureServices

Это опциональный метод в классе Startup, который используется для настройки сервисов для приложения. Когда в приложение поступает какой-либо запрос, сначала вызывается метод ConfigureService.

Метод ConfigureServices включает параметр IServiceCollection для регистрации сервисов. Этот метод должен быть объявлен с модификатором доступа public, чтобы среда могла читать контент из метаданных.

public void ConfigureServices(IServiceCollection services)  {     services.AddMvc();  }  

Метод Configure

Метод Configure используется для указания того, как приложение будет отвечать на каждый HTTP-запрос. Этот метод в основном используется для регистрации промежуточного программного обеспечения (middleware) в HTTP-конвейере. Этот метод принимает параметр IApplicationBuilder вместе с некоторыми другими сервисами, такими как IHostingEnvironment и ILoggerFactory. Как только мы добавим какой-либо сервис в метод ConfigureService, он будет доступен для использования в методе Configure.

public void Configure(IApplicationBuilder app)  {     app.UseMvc();  }

В приведенном выше примере показано, как включить функцию MVC в нашем фреймворке. Нам нужно зарегистрировать UseMvc() в Configure и сервис AddMvc() в ConfigureServices. UseMvc является промежуточным программным обеспечением. Промежуточное программное обеспечение (Middleware) это новая концепция, представленная в Asp.net Core. Для вас доступно множество встроенных промежуточных программ, некоторые из которых указаны ниже.

app.UseHttpsRedirection();  app.UseStaticFiles();  app.UseRouting();  app.UseAuthorization();  UseCookiePolicy();  UseSession(); 

Промежуточное программное обеспечение можно настроить в http-конвейере с помощью команд Use, Run и Map.

Run

Суть Run заключается в немедленном замыкании HTTP-конвейера. Это лаконичный способ добавления промежуточного программного обеспечения в конвейер, который не вызывает никакого другого промежуточного программного обеспечения, которое находится рядом с ним, и немедленно возвращает HTTP-ответ.

Use

Это передаст следующему (параметр next) делегату, так что HTTP-запрос будет передан следующему промежуточному программному обеспечению после выполнения текущего, если следующий делегат есть.

Map

Map просто принимает путь и функцию, которая конфигурирует отдельный конвейер промежуточного программного обеспечения.

app.Map("/MyDelegate", MyDelegate);

Чтобы получить более подробную информацию о промежуточном программном обеспечении, переходите сюда

ASP.net Core имеет встроенную поддержку внедрения зависимостей (Dependency Injection). Мы можем настроить сервисы для контейнера внедрения зависимостей, используя этот метод. Следующие способы конфигурационные методы в Startup классе.

AddTransient

Transient (временные) объекты всегда разные; каждому контроллеру и сервису предоставляется новый инстанс.

Scoped

Scoped используются одни и те же объекты в пределах одного запроса, но разные в разных запросах.

Singleton

Singleton объекты одни и те же для каждого объекта и каждого запроса.

Можем ли мы удалить startup.cs и объединить все в один класс с Program.cs?

Ответ: Да, мы можем объединить все классы запуска в один файл.

Заключение

Вы получили базовое понимание того, почему файлы program.cs и startup.cs важны для нашего приложения Asp.net Core и как их можно настроить. Мы также немного познакомились с переменной среды (Environment Variable), внедрение зависимостей, промежуточным программным обеспечением и как его настроить. Кроме того, мы увидели, как можно настроить UseIIsintegration() и UseKestrel().


Узнать подробнее о курсе C# ASP.NET Core разработчик.

Смотреть открытый вебинар по теме Отличия структурных шаблонов проектирования на примерах.

Подробнее..

Перевод Разбираемся с not в Python

15.02.2021 16:13:26 | Автор: admin

Привет, Хабр. В преддверии старта курса Python Developer. Professional подготовили традиционный перевод полезного материала.

Также приглашаем всех желающих посетить открытый вебинар на тему
Визуализация данных с помощью matplotlib.


В этой статье из серии про синтаксический сахар в Python я займусь на первый взгляд очень простым синтаксисом, но на самом деле, чтобы разобраться в механике его работы, нужно погрузиться вглубь на несколько слоев. Мы будем говорить о not.

Определение звучит на первый взгляд очень просто:

Оператор not выдает True, если его аргумент False, и False в противоположном случае.

Достаточно просто, не так ли? Но когда вы начинаете разбираться в том, что считать истинным или ложным, вы быстро понимаете, что есть приличное количество вещей, подходящих под эти определения.

(Как и в других статьях этой серии, код на С предназначен для тех, кто хочет пройти путь по хлебным крошкам, но вы можете пропустить его, если хотите)

Реализация not

Если вы посмотрите на байткод, то заметите единственный опкод, относящийся к not это UNARY_NOT.

Байткод для not a:

>>> import dis>>> def spam(): not a... >>> dis.dis(spam)  1           0 LOAD_GLOBAL              0 (a)              2 UNARY_NOT              4 POP_TOP              6 LOAD_CONST               0 (None)              8 RETURN_VALUE

Реализация UNARY_NOT по сути вызывает функцию С, которая называется PyObject_IsTrue() и возвращает обратное переданному значение: True для False, False для True.

Реализация опкода UNARY_NOT из Python/ceval.c:

case TARGET(UNARY_NOT): {            PyObject *value = TOP();            int err = PyObject_IsTrue(value);            Py_DECREF(value);            if (err == 0) {                Py_INCREF(Py_True);                SET_TOP(Py_True);                DISPATCH();            }            else if (err > 0) {                Py_INCREF(Py_False);                SET_TOP(Py_False);                DISPATCH();            }            STACK_SHRINK(1);            goto error;        }

Определение того, что такое True

Вся хитрость нашего разбора not начинается с определения того, что такое True. Если посмотреть на реализацию PyObject_IsTrue() на языке Си, станет видно, что существует несколько возможных способов выяснить истинность объекта.

Реализация PyObject_IsTrue():

/* Test a value used as condition, e.g., in a for or if statement.   Return -1 if an error occurred */intPyObject_IsTrue(PyObject *v){    Py_ssize_t res;    if (v == Py_True)        return 1;    if (v == Py_False)        return 0;    if (v == Py_None)        return 0;    else if (v->ob_type->tp_as_number != NULL &&             v->ob_type->tp_as_number->nb_bool != NULL)        res = (*v->ob_type->tp_as_number->nb_bool)(v);    else if (v->ob_type->tp_as_mapping != NULL &&             v->ob_type->tp_as_mapping->mp_length != NULL)        res = (*v->ob_type->tp_as_mapping->mp_length)(v);    else if (v->ob_type->tp_as_sequence != NULL &&             v->ob_type->tp_as_sequence->sq_length != NULL)        res = (*v->ob_type->tp_as_sequence->sq_length)(v);    else        return 1;    /* if it is negative, it should be either -1 or -2 */    return (res > 0) ? 1 : Py_SAFE_DOWNCAST(res, Py_ssize_t, int);}

Если разбираться в реализации на С, то правило выглядит так:

  1. Если True, то True

  2. Если False, то False

  3. Если None, то False

  4. То, что возвращает bool, до тех пор, пока возвращаемый объект является подклассом bool (то, что показывает вызов nb_bool)

  5. Вызов len() на объекте (то, за что отвечают вызовы mp_length и sq_length):

    1. Если больше 0, то True

    2. В противном случае False

  6. Если ничего из вышеперечисленного не подходит, то True

Все правила 1-3 и 6 достаточно понятны, а вот правила 4 и 5 требуют углубления в детали.

bool

Определение специального/волшебного метода bool говорит нам, что метод используется для реализации проверки истинности значений и должен возвращать либо True, либо False.

len()

Встроенная функция len() возвращает целое число, представляющее количество элементов в контейнере. Реализация вычисления длины объекта представлена слотами sq_length (длина последовательностей) и mp_length (длина словарей/мэпов).

Легко подумать, что к объекту можно просто обратиться и запросить его длину, но тут есть два слоя.

len

Первый слой это специальный/волшебный метод len. Как и следовало ожидать, он должен возвращать длину объекта, целое число >= 0. Но дело в том, что целочисленный не означает int, а означает объект, который вы можете преобразовать без потерь к целочисленному объекту. И как же выполнить это преобразование?

index

Чтобы без потерь преобразовать численный объект в целочисленный, используется специальный/волшебный метод index. В частности, для обработки преобразования используется функция PyNumber_Index(). Эта функция слишком длинная, чтобы ее сюда вставлять, но делает она следующее:

  1. Если аргумент является экземпляром класса int, вернуть его

  2. В противном случае вызвать index на объекте

  3. Если index возвращает конкретный экземпляр класса int, вернуть его (технически возвращать подкласс не рекомендуется, но давайте оставим это в прошлом).

  4. В противном случае вернуть TypeError.

На уровне Python это делается с помощью operator.index(). К сожалению, здесь не реализуется семантика PyNumber_Index(), поэтому на самом деле с точки зрения not и len, функция работает немного неточно. Если бы она все же была реализована, то выглядела бы так:

Реализация PyNumber_Index() на Python:

def index(obj: Object, /) -> int:    """Losslessly convert an object to an integer object.    If obj is an instance of int, return it directly. Otherwise call __index__()    and require it be a direct instance of int (raising TypeError if it isn't).    """    # https://github.com/python/cpython/blob/v3.8.3/Objects/abstract.c#L1260-L1302    if isinstance(obj, int):        return obj    length_type = builtins.type(obj)    try:        __index__ = _mro_getattr(length_type, "__index__")    except AttributeError:        msg = (            f"{length_type!r} cannot be interpreted as an integer "            "(must be either a subclass of 'int' or have an __index__() method)"        )        raise TypeError(msg)    index = __index__(obj)    # Returning a subclass of int is deprecated in CPython.    if index.__class__ is int:        return index    else:        raise TypeError(            f"the __index__() method of {length_type!r} returned an object of "            f"type {builtins.type(index).__name__!r}, not 'int'"        )

Реализация len()

Еще один интересный факт о реализации len(): она всегда возвращает конкретный int. Так, несмотря на то, что index() или len() могут возвращать подкласс, ее реализация на уровне С через PyLong_FromSsize_t() гарантирует, что всегда будет возвращаться конкретный экземпляр int.

В противном случае len() будет проверять, что возвращают len() и index (), например, является ли возвращаемый объект подклассом int, больше ли значение или равно 0 и т.д. Таким образом, вы можете реализовать len() так:

def len(obj: Object, /) -> int:    """Return the number of items in a container."""    # https://github.com/python/cpython/blob/v3.8.3/Python/bltinmodule.c#L1536-L1557    # https://github.com/python/cpython/blob/v3.8.3/Objects/abstract.c#L45-L63    # https://github.com/python/cpython/blob/v3.8.3/Objects/typeobject.c#L6184-L6209    type_ = builtins.type(obj)    try:        __len__ = _mro_getattr(type_, "__len__")    except AttributeError:        raise TypeError(f"type {type!r} does not have a __len__() method")    length = __len__(obj)    # Due to len() using PyObject_Size() (which returns Py_ssize_t),    # the returned value is always a direct instance of int via    # PyLong_FromSsize_t().    index = int(_index(length))    if index < 0:        raise ValueError("__len__() should return >= 0")    else:        return index

Реализация operator.truth()

Во многих языках программирования при определении операции not, распространенной идиомой является превращение объекта в его сравнительное логическое значение с помощью передачи его в not дважды черезnot not. Первый раз, чтобы получить инвертированное логическое значение, и второй раз, чтобы инвертировать инверсию, и получить логическое значение, которое вы хотели изначально.

В Python нам не нужна эта идиома. Спасибо bool() (а конкретно bool.new()), за то, что у нас есть вызов функции, который мы можем использовать для получения конкретного логического значения, а именно operator.truth(). Если вы посмотрите на этот метод, то увидите, что он использует PyObject_IsTrue() для определения логического значения объекта. Посмотрев slot_nb_bool (), вы увидите, что в конечном итоге он делает то же, что и PyObject_IsTrue(). То есть, если мы можем реализовать аналог PyObject_IsTrue(), то можем определить, какое логическое значение имеет объект.

По старой схеме и с тем, что мы узнали только что, мы можем реализовать operator.truth() для этой логики (я предпочитаю не реализовывать bool, поскольку не хочу реализовывать все его численные функции, и не придумал хорошего способа сделать True и False с нуля, которые наследовались бы от 1 и 0 на чистом Python).

Реализация operator.truth():

def truth(obj: Any, /) -> bool:    """Return True if the object is true, False otherwise.    Analogous to calling bool().    """    if obj is True:        return True    elif obj is False:        return False    elif obj is None:        return False    obj_type = type(obj)    try:        __bool__ = debuiltins._mro_getattr(obj_type, "__bool__")    except AttributeError:        # Only try calling len() if it makes sense.        try:            __len__ = debuiltins._mro_getattr(obj_type, "__len__")        except AttributeError:            # If all else fails...            return True        else:            return True if debuiltins.len(obj) > 0 else False    else:        boolean = __bool__(obj)        if isinstance(boolean, bool):            # Coerce into True or False.            return truth(boolean)        else:            raise TypeError(                f"expected a 'bool' from {obj_type.__name__}.__bool__(), "                f"not {type(boolean).__name__!r}"            )

Реализация not

С реализованным оператором operator.truth(), реализовать operator.not_() дело всего одной лямбды:

lambda a, /: False if truth(a) else True

Итоговый результат прост, но, чтобы добраться до него, нужно было проделать немало работы.

Как обычно, код из этой статьи можно найти в моем проекте desugar.


Узнать подробнее о курсе Python Developer. Professional.

Смотреть открытый вебинар на тему Визуализация данных с помощью matplotlib.

Подробнее..

Перевод Ускоряем код на Python с помощью Nim

20.02.2021 12:17:51 | Автор: admin

Привет, хабровчане. В преддверии старта курса "Python Developer. Basic" подготовили для вас перевод интересной статьи. Также приглашаем на открытый вебинар Карьера для "Python Developer. Basic".


Python один из самых популярных и доступных языков программирования, но далеко не самый быстрый. Многие создатели библиотек и фреймворков прибегали к использованию расширения на С, чтобы их код работал быстрее, чем код на нативном Python.Этот способ вполне рабочий, но если вы не знакомы с С, сборка мусора и управление памятью станут вашим адом на Земле. И тут на сцену выходит Nim.

Что такое Nim?

Nim статически типизированный, компилируемый, объектно-ориентированный язык программирования. Nim создавался, чтобы быть таким же быстрым как С и таким же выразительным как Python, и к тому же, расширяемым как Lisp. Благодаря синтаксическому сходству с Python, Nim станет отличным выбором языка для расширения, если с C вам не по пути.

Начало работы с Nim

Чтобы начать писать на Nim, его нужно установить в свою систему. Скачайте и установите его с сайта nim-lang.org.

Как только вы закончите, мы начнем писать свой первый код на Nim. Во-первых, мы создаем файл, я назову его hello.nim, а вы можете назвать его как захотите. Теперь мы откроем его в любом текстовом редакторе и напишем:

static:    echo("Hello, world!")

Сохраните и закройте файл, затем откройте терминал из текущей директории и введите:

nim compile hello.nim

И вуаля! В консоль выведется Hello, World!. После получения первого представления о Nim перейдем к основной теме статьи.

Встраиваем Nim в приложения на Python

Nim поставляется с модулем nimpy и nimporter, которые доступны для Python.Последний можно установить с помощью pip install nimporter. Эти два пакета будут иметь важное значение при совместной работе двух языков.

Чтобы продемонстрировать возможности Nim, мы создадим простой тест, который будет сравнивать скорость работы обоих языков, на примере функции находящей n-ое число последовательности Фибоначчи.

Давайте создадим папку под названием Benchmark с 3 файлами внутри:

  • main.py файл, который мы будем запускать

  • nmath.nim файл с версией функции fib на Nim

  • pmath.py файл с версией функции fib на Python

Сначала напишем функцию fib на Python:

def fib(n):    if n == 0:        return 0    elif n < 3:        return 1    return fib(n - 1) + fib(n - 2)

А теперь переместимся в nmath.nim. Для начала нам нужно импортировать nimpy:

import nimpy

Прямо как в Python, не так ли? А теперь сама функция:

import nimpyproc fib(n: int): int {.exportpy.} =    if n == 0:        return 0    elif n < 3:        return 1    return fib(n-1) + fib(n-2)

Давайте разберемся

Мы определяем функцию fib с помощью ключевого слова proc. Дальше указываем тип возвращаемого значения как целочисленный, а (вау, что это такое?) {.exportpy.} сигнализирует Nim, что эта функция предназначена для использования в другом модуле Python. В остальном все также, как в Python.

Тестирование на время

В main.py создадим простой бенчмарк:

import nimporterfrom time import perf_counterimport nmath  # Nim imports!import pmathprint('Measuring Python...')start_py = perf_counter()for i in range(0, 40):    print(pmath.fib(i))end_py = perf_counter()print('Measuring Nim...')start_nim = perf_counter()for i in range(0, 40):    print(nmath.fib(i))end_nim = perf_counter()print('---------')print('Python Elapsed: {:.2f}'.format(end_py - start_py))print('Nim Elapsed: {:.2f}'.format(end_nim - start_nim))

Вот и все!

Пакет importer позволяет импортировать Nim в обычные модули Python, которые будут использоваться также, как и собственные. Круто, не правда ли?

Чтобы запустить код, просто введите python main.py в командную строку и смотрите, что будет!

Python Elapsed: 33.60Nim Elapsed: 1.05

Заключение

Вот и все, что нужно сделать, чтобы быстро встроить Nim в Python. Nim оказался примерно в 30 раз быстрее, но имейте ввиду, что разница в скорости может варьироваться в зависимости от выполняемой задачи. Чтобы узнать больше о Nim, рекомендую ознакомиться с официальной документацией и почитать про него в Википедии.

Что ж, на этом я закончу свой туториал! Спасибо, что дотерпели до конца. Надеюсь, эта статья окажется для вас полезной.


Узнать подробнее о курсе "Python Developer. Basic".

Смотреть запись открытого demo-урока Три кита: map(), filter() и zip().

Подробнее..

Перевод Реализуем глобальную обработку исключений в ASP.NET Core приложении

20.02.2021 18:04:03 | Автор: admin

В преддверии старта курса C# ASP.NET Core разработчик подготовили традиционный перевод полезного материала.

Также рекомендуем посмотреть вебинар на тему
Отличия структурных шаблонов проектирования на примерах. На этом открытом уроке участники вместе с преподавателем-экспертом познакомятся с тремя структурными шаблонами проектирования: Заместитель, Адаптер и Декоратор.


Введение

Сегодня в этой статье мы обсудим концепцию обработки исключений в приложениях ASP.NET Core. Обработка исключений (exception handling) одна из наиболее важных импортируемых функций или частей любого типа приложений, которой всегда следует уделять внимание и правильно реализовывать. Исключения это в основном средства ориентированные на обработку рантайм ошибок, которые возникают во время выполнения приложения. Если этот тип ошибок не обрабатывать должным образом, то приложение будет остановлено в результате их появления.

В ASP.NET Core концепция обработки исключений подверглась некоторым изменениям, и теперь она, если можно так сказать, находится в гораздо лучшей форме для внедрения обработки исключений. Для любых API-проектов реализация обработки исключений для каждого действия будет отнимать довольно много времени и дополнительных усилий. Но мы можем реализовать глобальный обработчик исключений (Global Exception handler), который будет перехватывать все типы необработанных исключений. Преимущество реализации глобального обработчика исключений состоит в том, что нам нужно определить его всего лишь в одном месте. Через этот обработчик будет обрабатываться любое исключение, возникающее в нашем приложении, даже если мы объявляем новые методы или контроллеры. Итак, в этой статье мы обсудим, как реализовать глобальную обработку исключений в ASP.NET Core Web API.

Создание проекта ASP.NET Core Web API в Visual Studio 2019

Итак, прежде чем переходить к обсуждению глобального обработчика исключений, сначала нам нужно создать проект ASP.NET Web API. Для этого выполните шаги, указанные ниже.

  • Откройте Microsoft Visual Studio и нажмите Create a New Project (Создать новый проект).

  • В диалоговом окне Create New Project выберите ASP.NET Core Web Application for C# (Веб-приложение ASP.NET Core на C#) и нажмите кнопку Next (Далее).

  • В окне Configure your new project (Настроить новый проект) укажите имя проекта и нажмите кнопку Create (Создать).

  • В диалоговом окне Create a New ASP.NET Core Web Application (Создание нового веб-приложения ASP.NET Core) выберите API и нажмите кнопку Create.

  • Убедитесь, что флажки Enable Docker Support (Включить поддержку Docker) и Configure for HTTPS (Настроить под HTTPS) сняты. Мы не будем использовать эти функции.

  • Убедитесь, что выбрано No Authentication (Без аутентификации), поскольку мы также не будем использовать аутентификацию.

  • Нажмите ОК.

Используем UseExceptionHandler middleware в ASP.NET Core.

Чтобы реализовать глобальный обработчик исключений, мы можем воспользоваться преимуществами встроенного Middleware ASP.NET Core. Middleware представляет из себя программный компонент, внедренный в конвейер обработки запросов, который каким-либо образом обрабатывает запросы и ответы. Мы можем использовать встроенное middleware ASP.NET Core UseExceptionHandler в качестве глобального обработчика исключений. Конвейер обработки запросов ASP.NET Core включает в себя цепочку middleware-компонентов. Эти компоненты, в свою очередь, содержат серию делегатов запросов, которые вызываются один за другим. В то время как входящие запросы проходят через каждый из middleware-компонентов в конвейере, каждый из этих компонентов может либо обработать запрос, либо передать запрос следующему компоненту в конвейере.

С помощью этого middleware мы можем получить всю детализированную информацию об объекте исключения, такую как стектрейс, вложенное исключение, сообщение и т. д., а также вернуть эту информацию через API в качестве вывода. Нам нужно поместить middleware обработки исключений в configure() файла startup.cs. Если мы используем какое-либо приложение на основе MVC, мы можем использовать middleware обработки исключений, как это показано ниже. Этот фрагмент кода демонстрирует, как мы можем настроить middleware UseExceptionHandler для перенаправления пользователя на страницу с ошибкой при возникновении любого типа исключения.

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)  {      app.UseExceptionHandler("/Home/Error");      app.UseMvc();  } 

Теперь нам нужно проверить сообщение об исключении. Для этого откройте файл WeatherForecastController.cs и добавьте следующий экшн-метод, чтобы пробросить исключение:

[Route("GetExceptionInfo")]  [HttpGet]  public IEnumerable<string> GetExceptionInfo()  {       string[] arrRetValues = null;       if (arrRetValues.Length > 0)       { }       return arrRetValues;  } 

Если мы хотим получить подробную информацию об объектах исключения, например, стектрейс, сообщение и т. д., мы можем использовать приведенный ниже код в качестве middleware исключения

app.UseExceptionHandler(                  options =>                  {                      options.Run(                          async context =>                          {                              context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;                              context.Response.ContentType = "text/html";                              var exceptionObject = context.Features.Get<IExceptionHandlerFeature>();                              if (null != exceptionObject)                              {                                  var errorMessage = $"<b>Exception Error: {exceptionObject.Error.Message} </b> {exceptionObject.Error.StackTrace}";                                  await context.Response.WriteAsync(errorMessage).ConfigureAwait(false);                              }                          });                  }              );  

Для проверки вывода просто запустите эндпоинт API в любом браузере:

Определение пользовательского Middleware для обработки исключений в API ASP.NET Core

Кроме того, мы можем написать собственное middleware для обработки любых типов исключений. В этом разделе мы продемонстрируем, как создать типичный пользовательский класс middleware. Пользовательское middleware также обеспечивает гораздо большую гибкость для обработки исключений. Мы можем добавить стекатрейс, имя типа исключения, код ошибки или что-нибудь еще, что мы захотим включить как часть сообщения об ошибке. В приведенном ниже фрагменте кода показан типичный пользовательский класс middleware:

using Microsoft.AspNetCore.Http;    using Newtonsoft.Json;    using System;    using System.Collections.Generic;    using System.Linq;    using System.Net;    using System.Threading.Tasks;        namespace API.DemoSample.Exceptions    {        public class ExceptionHandlerMiddleware        {            private readonly RequestDelegate _next;                public ExceptionHandlerMiddleware(RequestDelegate next)            {                _next = next;            }                public async Task Invoke(HttpContext context)            {                try                {                    await _next.Invoke(context);                }                catch (Exception ex)                {                                    }            }        }    } 

В приведенном выше классе делегат запроса передается любому middleware. Middleware либо обрабатывает его, либо передает его следующему middleware в цепочке. Если запрос не успешен, будет выброшено исключение, а затем будет выполнен метод HandleExceptionMessageAsync в блоке catch. Итак, давайте обновим код метода Invoke, как показано ниже:

public async Task Invoke(HttpContext context)  {      try      {          await _next.Invoke(context);      }      catch (Exception ex)      {          await HandleExceptionMessageAsync(context, ex).ConfigureAwait(false);      }  }  

Теперь нам нужно реализовать метод HandleExceptionMessageAsync, как показано ниже:

private static Task HandleExceptionMessageAsync(HttpContext context, Exception exception)  {      context.Response.ContentType = "application/json";      int statusCode = (int)HttpStatusCode.InternalServerError;      var result = JsonConvert.SerializeObject(new      {          StatusCode = statusCode,          ErrorMessage = exception.Message      });      context.Response.ContentType = "application/json";      context.Response.StatusCode = statusCode;      return context.Response.WriteAsync(result);  } 

Теперь, на следующем шаге, нам нужно создать статический класс с именем ExceptionHandlerMiddlewareExtensions и добавить приведенный ниже код в этот класс,

using Microsoft.AspNetCore.Builder;  using System;  using System.Collections.Generic;  using System.Linq;  using System.Threading.Tasks;    namespace API.DemoSample.Exceptions  {      public static class ExceptionHandlerMiddlewareExtensions      {          public static void UseExceptionHandlerMiddleware(this IApplicationBuilder app)          {              app.UseMiddleware<ExceptionHandlerMiddleware>();          }      }  }  

На последнем этапе, нам нужно включить наше пользовательское middleware в методе Configure класса startup, как показано ниже:

app.UseExceptionHandlerMiddleware();

Заключение

Обработка исключений это по сути сквозная функциональность для любого типа приложений. В этой статье мы обсудили процесс реализации концепции глобальной обработки исключений. Мы можем воспользоваться преимуществами глобальной обработки исключений в любом приложении ASP.NET Core, чтобы гарантировать, что каждое исключение будет перехвачено и вернет правильные сведения, связанные с этим исключением. С глобальной обработкой исключений нам достаточно в одном месте написать код, связанный с обработкой исключений, для всего нашего приложения. Любые предложения, отзывы или запросы, связанные с этой статьей, приветствуются.


Узнать подробнее о курсе C# ASP.NET Core разработчик.

Посмотреть вебинар на тему Отличия структурных шаблонов проектирования на примерах.

Подробнее..

Перевод Scala 3 Dotty Факты и Мнения. Что мы ожидаем?

04.03.2021 20:15:20 | Автор: admin

Привет, Хабр. Для будущих студентов курса Scala-разработчик подготовили перевод материала.

Приглашаем также на открытый вебинар Эффекты в Scala. Участники вместе с экспертом рассмотрят понятие эффекта и сложности, которые могут возникать при их наличии, а также рассмотрят понятие функционального эффекта и его свойства.


Что такое Scala 3?

Scala 3 это новая основная версия языка программирования Scala. Это результат многолетних исследований, разработок и сотрудничества между компаниями и организациями, которые координируют развитие Scala с помощью многих других людей и организаций, и которые вкладывают свое свободное время, чтобы сделать это возможным. Эти совместные усилия принесли нам наиболее заметные изменения в языке.

Что мотивировало появление новой версии, которая связана с самой сутью Scala (а именно DOT-вычисления причина, по которой Scala 3 начиналась как Dotty); в новой версии наблюдается повышение производительности и предсказуемости, что делает код более легким, интересным и безопасным; улучшение инструментария и бинарной совместимости; а также еще более дружелюбное отношение к новичкам.

В этой статье мы выделим некоторые изменения, которые, по нашему мнению, имеют большую ценность для начинающих программистов Scala. Мы также поговорим о процессе миграции и бинарной совместимости. Наконец, в конце мы поделимся нашим мнением об этой новой версии.

Scala 3 новый язык?

Да, это так, потому что в Scala есть много изменений в языке, включая функции, которые будут постепенно выводиться из употребления. А также потому, что процесс обучения отличается от предыдущих версий. Кроме того, там также будет обновляться база знаний.

И одновременно, мы можем ответить нет на данный вопрос выше. Несмотря на изменения, о которых мы расскажем в этой статье, вдобавок ко многим другим, не упомянутым (чтобы статья не была слишком длинной), Scala 3 все еще остается Scala. Основные концепции остаются на месте, а поддержка кросс-билдинга усиливает то, что набор функций имеет большое пересечение с предыдущей версией.

Почему происходит столь много изменений одновременно?

В конце этой статьи вы будете задаваться вопросом, почему там сразу много меняется. Ответ в книге Scala 3. Она представляет собой сам язык. Выпуская все изменения сразу, нет необходимости каждый раз переписывать книгу, так как это делается с многими другими книгами. В основном, изменения затрагивают основы языка, упрощая жизнь его пользователей или заменяя существующие функции. Поэтому предстоящие изменения должны быть ограничены и расставлены по приоритетам в соответствии с основами, упрощениями и ограничениями. Тогда всё, что остаётся изменить в любых возможных более поздних версиях, будет больше связано с добавлением большей функциональности и выразительности, особенно для опытных пользователей, т.е. вещей, которые можно отложить и которые не оказывают резкого влияния на язык.

Scala 3 это новый Python 3?

Существует необоснованное убеждение, что Scala 3 это новый Python 3 относительно его совместимости с предыдущей версией. Однако, есть некоторые аргументы против этого мнения: а именно, что вам не нужно переносить все на Scala 3, так как есть бинарная совместимость с Scala 2.13 (подробнее об этом будет в разделе о миграции); вы можете уверенно мигрировать благодаря сильной системе типа Scala; и есть гораздо больше преимуществ при миграции с 2 на 3 в Scala, чем было бы при миграции с 2 на 3 на Python 3.

Какие изменения ключевые?

Мы выбрали некоторые ключевые функции, которые считаем более актуальными для начинающих программистов Scala. Мы их опишем и прокомментируем то, как они могут на нас повлиять. Мы не будем комментировать все новые возможности, потому что список слишком длинный. В любом случае, эта статья не будет учебным пособием по каждой функции. Если вы хотите увидеть список всех изменений, ссылок и других ресурсов, вы можете посмотреть это на dotty.epfl.ch.

Optional Braces (опциональные или необязательные фигурные скобки)

Одной из самых революционных новинок являются optional braces и использование правил отступа, как в случае с Python. Это действительно революционно, потому что визуально меняется код и это влияет на читабельность достаточно, чтобы невнимательный читатель подумал, что это новый язык. В дополнение к тому, что это приводит к более чистому и короткому коду. Optional braces хорошая вещь, потому что:

  1. Мы зачастую пытаемся опустить фигурные скобки там, где это возможно (например, для методов/функций, которые состоят из одного выражения);

  2. Даже в случае со скобками почти каждый проект использует некоторые правила для отступов очень строго (это проверяется либо при просмотре кода, либо при принудительном применении scalafmt), так что скобки являются для нас лишь дополнительным токеном для регистрации во время чтения кода, и это не вводит никакой дополнительной информации.)

trait Printer:  def print(msg: String): Unitclass ConsolePrinter extends Printer:  def print(msg: String): Unit = println(msg)class EmojiPrinter(underlying: Printer) extends Printer:  def print(msg: String): Unit =    val emoji = msg match      case ":)"  => ?      case ":D"  => ?      case ":|"  => ?      case other => other    underlying.print(emoji)

Одним из недостатков использования правил отступов является распознавание того момента, когда заканчивается большая область отступов. Для решения этой проблемы Scala 3 предлагает end маркер.

class EmojiPrinter(underlying: Printer) extends Printer:  def print(msg: String): Unit =    if msg != null then      val emoji = msg match        case ":)"  => ?        case ":D"  => ?        case ":|"  => ?        case other => other      underlying.print(emoji)    end ifend EmojiPrinter

Обратите внимание, что мы не ставим скобки, а также обратите внимание на then.

Оба эти и другие изменения являются частью нового синтаксиса управления еще одно изменение, которое визуально влияет на код.

Помимо увеличения кодовой базы по сравнению с опцией работы со скобками, преимущество end маркеров по сравнению со скобками заключается в том, что вы можете маркировать замыкание. Таким образом, становится легче найти тот компонент, который замыкается.

Не рекомендуется повсеместно ставить end маркер. Подсказка такая использовать его, когда область отступов слишком длинная. Однако, определение слишком длинный может варьироваться от человека к человеку. Таким образом, в соответствии с официальной документацией, end маркер имеет смысл если:

  • Конструктор содержит пустые строки, или

  • Конструктор имеет 15 линий и более

  • Конструктор имеет 4 уровня отступов и более

Согласно официальной документации, опциональные скобки являются экспериментальной функцией, т.е. их можно деактивировать. Кроме того, компилятор предупредит о любом плохо откорректированном коде. Более того, правило заключается в том, что добавление пары опциональных скобок не изменит смысла хорошо продуманной программы.

Enums

Почти каждый программист Scala, работающий на Java, пропускает ключевое слово enum и концепцию, которую он воплощает. Представим, что до Scala 3 вам нужно было написать какой-нибудь шаблонный код, чтобы достичь чего-то похожего на перечисление:

sealed trait Colorcase object Red extends Colorcase object Green extends Colorcase object Blue extends Color

В Scala 3, мы можем использовать стандартные типы enum:

enum Color:  case Red, Blue, Green

С годами все больше и больше код пишется с учетом безопасности типа. Такие концепции, как алгебраические типы данных (ADT), стали обычным явлением в системном моделировании. Поэтому было бы целесообразно предложить программистам более простой механизм реализации этих структур данных. Действительно, Scala 3 предлагает более простой способ реализации ADT через enums:

enum Option[+T]:  case Some(x: T) // extends Option[T]       (omitted)  case None       // extends Option[Nothing] (omitted)

Если вы хотите сделать ваш определенный Scala-enum совместимым с Java-enum, вам необходимо расширить java.lang.Enum, который импортируется по умолчанию:

enum Color extends Enum[Color]:  case Red, Blue, Greenprintln(Color.Green.compareTo(Color.Red)) // 2

Если вам интересен более сложный случай перечисления, например, с параметрами или обобщенными ADT, посмотрите на ссылку enums.

Редизайн implicit (неявность)

Несмотря на критику, implicit является одной из наиболее характерных черт Scala. Однако, она также является одной из самых противоречивых. Есть свидетельства о том, что implicit скорее является механизмом, чем реальным намерением, которое заключается в решении проблем. Более того, несмотря на то, что implicit легко сочетается с множеством конструкторов, становится не так легко, когда речь заходит о предотвращении нарушений и неправомерного использования. Поэтому Scala 3 редизайнит особенности implicit, ставя каждый случай использования на своё место. Ниже приведены изменения, которые мы считаем наиболее актуальными в отношении implicit редизайна.

Implicit определения Заданные экземпляры

В данном случае речь идет о том, как Scala 3 использует синтез контекстных параметров для определенного типа. Она заменяет предыдущее implicit использование для этой цели. В Scala 3 вы можете дополнительно указывать имя заданного экземпляра. Если вы пропустите это имя, то компилятор выведет его.

trait Ord[T]:  def compare(a: T, b: T): Intgiven intOrd: Ord[Int] with // with name  def compare(a: Int, b: Int): Int = a - bgiven Order[String] with // without name  def compare(a: String, b: String): Int = a.compareTo(b)

Implicit параметры Использование clauses

Контекстные параметры (или implicit параметры) помогают избежать написания повторяющихся параметров по цепочке вызовов. В Scala 3 вы используете implicit параметры через ключевое слово using. Например, из приведенных выше примеров можно определить функцию min , которая работает с ними.

def min[T](a: T, b: T)(using ord: Ord[T]): T =  if ord.compare(a, b) < 0 then a else bmin(4, 2)min(1, 2)(using intOrd)min("Foo", "Bar")

Когда вам нужно просто переадресовать параметры контекста, вам не нужно их называть.

def printMin[T](a: T, b: T)(using Ord[T]): Unit =  println(min(a, b))

Implicit Импорт Заданный Импорт

Бывают случаи, когда неправильный implicit импорт может стать причиной проблемы Кроме того, некоторые инструменты, такие как IDE и генераторы документации, не справляются с implicit импортом. Scala 3 предоставляет новый способ отличить заданный импорт от обычного.

object A:  class TC  given tc: TC = ???  def f(using TC) = ???object B:  import A._  import A.given  ...

В приведенном выше примере нам пришлось импортировать заданные импорты отдельно, даже после импорта с помощью wildcad (_), потому что в Scala 3 заданные импорты работают не так, как обычные. Вы можете объединить оба импорта в один.

object C:  import A.{using, _}

Вот некоторые спецификации, касающиеся заданных импортов по типам. Посмотрите на данную импортную документацию.

Implicit Conversion Заданная Conversion

Представим, что до Scala 3, если вы хотели определить Implicit Conversion, вам просто нужно было написать Implicit Conversion, которое получает экземпляр исходного типа и возвращает экземпляр целевого типа. Теперь нужно определить данный экземпляр классаscala.Conversion, который ведет себя как функция. Действительно, экземплярыscala. Conversion это функции. Посмотрите на их определение.

abstract class Conversion[-T, +U] extends (T => U):  def apply (x: T): U

Например, здесь можно посмотреть на преобразование от Int к Double и на более короткую версию:

given int2double: Conversion[Int, Double] withdef apply(a: Int): Double = a.toDoublegiven Conversion[Int, Double] = _.toDouble

Основной причиной использования Заданной Conversion является наличие специального механизма преобразования стоимости без каких-либо сомнительных конфликтов с другими конструкторами языка. Согласно данной документации по преобразованию, все другие формы implicit преобразований будут постепенно ликвидированы.

Implicit классы Методы расширения

Методы расширения являются более интуитивным и менее шаблонным способом, чем Implicit классы для добавления методов к уже определенным типам.

case class Image(width: Int, height: Int, data: Array[Byte])extension (img: Image)  def isSquare: Boolean = img.width == img.heightval image = Image(256, 256, readBytes("image.png"))println(image.isSquare) // true

Методы расширения могут иметь параметры типа как в их определении, так и в их методах. Их определения также могут иметь несколько методов.

extension [T](list: List[T])def second: T = list.tail.headdef heads: (T, T) = (list.head, second)

Как видите, методы расширения гораздо чище, чем написание implicit классов. Обратите внимание, что в отличие от implicitклассов, вам не нужно называть определения расширений.

Типы пересечения и соединения

Scala 3 предоставляет новые способы объединения типов, два из которых Типы пересечения и соединения.

Типы пересечения

Типы пересечения это типы, элементы которых принадлежат к обоим типам, составляющим его. Они определяются оператором & более двух типов. & являются коммутаторами: A & B производит один и тот же тип B & A. Они также могут быть сцеплены, так как они тоже являются типами.

trait Printable[T]: def print(x: T): Unittrait Cleanable: def clean(): Unittrait Flushable: def flush(): Unitdef f(x: Printable[String] & Cleanable & Flushable) = x.print("working on...") x.flush() x.clean()

Вам может быть интересно узнать, как компилятор решает конфликты разделяемых элементов. Ответ состоит в том, что компилятор в этом не нуждается. Типы пересечений представляют собой требования к members типам (члены типов). Они работают практически так же, как и при формировании типов. В момент построения members необходимо просто убедиться, что все полученные members определены корректно.

trait A:  def parent: Option[A]trait B:  def parent: Option[B]class C extends A,B:  def parent: Option[A & B] = None  // or  // def parent: Option[A] & Option[B] = Nildef work(x: A & B) =  val parent:[A & B] = x.parent  // or  // val parent: Option[A] & Option[B] = x.parent  println(parent) // Nonework(new C)

Заметьте, что в in class C нам нужно решить конфликты связанные с тем, что children member появляется и в A и в B. То есть тип C это пересечение его типа в A и его типа в B, например, Option[A] & Option[B] могут быть упрощены в вид Option[A & B], так как Option (опция) является ковариантной.

Типы соединения

Тип соединения A | B принимает все экземпляры типа A и все экземпляры типа B. Обратите внимание, что мы говорим об экземплярах, а не о members (членах), как это делают типы пересечения. Поэтому, если мы хотим получить доступ к его members, нам нужно сопоставить их по шаблону.

def parseFloat(value: String | Int): Float =   value match     case str: String => str.toFloat    case int: Int => int.floatValueparseFloat("3.14") // 3.14parseFloat(42) // 42.0

Типы соединения не выводятся автоматически. Если вы хотите, чтобы тип определения (val, var или def) был типом соединения, вам нужно сделать это явно, иначе компилятор выведет наименьший общий ancestor (предок).

val any = if (cond) 42 else "3.14" // Anyval union: String | Int = if (cond) 42 else "3.14" // String | Int

Почётные упоминания

Некоторые другие изменения довольно актуальны и также заслуживают здесь упоминания.

Трейт параметры

Scala 3 позволяет трейтам иметь параметры. Эти параметры оцениваются непосредственно перед инициализацией трейта. Параметры трейта являются заменой для ранних инициализаторов, которые были удалены из Scala 3.

Универсальные применяемые методы

Конструкторы Case class стали достаточно популярными, и многие разработчики пишут Case class просто для того, чтобы не писать new для создания объектов. Поэтому в Scala 3 больше не нужно писать new для создания экземпляров классов.

Типы Opaque

Opaque-типы обеспечивают абстракцию типа без каких-либо перегрузок. Модифицируя определение типа с помощью Opaque, вы ограничиваете тот факт, что определение типа является просто псевдонимом для другого типа, где оно определено. Для клиентов своей области видимости Opaque-типы ведут себя идеально как тип, а не просто как псевдоним. Таким образом, например, вы не можете предполагать существование псевдонима для создания значений псевдонима и присвоения определений Opaque-типу.

Export clauses

Export clauses это простой способ передачи members (членов) от одного типа к другому без какого-либо наследования. Откладывая export от членов-класса (включая трейты и объекты) в тело другого класса (также включая трейты и объекты), вы копируете members и делаете их доступными через экземпляры целевых классов.

Редизайн метапрограммирования

В Scala 2 макросы оставались экспериментальной функцией. В связи с тем, что макросы сильно зависят от компилятора Scala 2, поэтому мигрировать на Scala 3 оказалось невозможным. В метапрограммировании Scala 3 появились новые конструкции, облегчающие его использование. Взгляните на обзор метапрограммирования Scala 3.

Ограничения и удаленные фитчи

Чтобы упростить язык и сделать его более безопасным, Scala 3 ограничивает количество опций и снимает некоторые функции с производства. Самые важные из них:

  • Ограничение проекций типов (C#P) только в отношении классов, т.е. абстрактные типы больше не поддерживают их;

  • Для использования надстрочной нотации, модификатор infix должен быть помечен на желаемых методах;

  • Мультиверсальное равенство - это оптический способ избегания неожиданных равнозначностей;

  • Implicit преобразования и приведенные выше импорты также являются видами ограничений;

  • Специальная обработка трейта DelayedInit больше не поддерживается;

  • Отброшен синтаксис процедуры (опускание типа возврата и = при определении функции);

  • Библиотеки XML все еще поддерживаются, но будут удалены в ближайшем будущем;

  • Автоматическое приложение, когда пустой список аргументов () неявно вставляется при вызове метода без аргументов и больше не поддерживается;

  • Символьные литералы больше не поддерживаются.

Полный список выпавших функций доступен в официальной документации.

Нужно ли вам переходить на Scala 3?

Прежде всего, есть очень хорошо сделанная документация, посвященная исключительно переходу на Scala 3. Здесь мы просто поделимся некоторыми мыслями, которые вы можете учесть, когда начнете использовать Scala 3 в своих текущих проектах.

Хорошо известно, что рекомендуется быть в курсе работы со стеком технологий и зависимостями, так как они могут исправлять ошибки и улучшать удобство использования, производительность и так далее. Это также относится и к Scala. Независимо от того, насколько мало усилий прилагается для перехода, иногда необходимо согласовать это с заинтересованными сторонами. Тем не менее, миграция Scala 3 была спроектирована так, чтобы быть как можно более плавной. Это означает, что вы можете воспользоваться самыми заметными эволюциями в языке и сделать программирование более легким, интересным и безопасным.

Какое время подходит для перехода на Scala 3?

Мы хотели бы порекомендовать вам перейти на Scala 3 прямо сейчас, но мы знаем, что есть факторы, выходящие за рамки возможностей даже большого энтузиазма. Если следующее звучит как отличный аргумент, чтобы убедить вашего руководителя, то не забывайте, что Scala 3 сохраняет как обратную, так и прямую совместимость с Scala 2.13 (за исключением макросов), не всю совместимость, но при каждой несовместимости есть решение для кросс-компиляции.

Что такое бинарная совместимость в Scala 3?

Scala 3 предлагает обратную двоичную совместимость с Scala 2. Это означает, что вы все еще можете полагаться на библиотеку Scala 2. Начиная с версии Scala 2.13.4, выпущенной в ноябре 2020 года, вы можете использовать библиотеки, написанные на Scala 3. Таким образом, в Scala 3 вы получаете двойную совместимость туда и обратно.

Scala 3 поддерживает обратную и прямую совместимость с помощью уникального революционного механизма в экосистеме Scala. Scala 3 выводит файлы TASTy и поддерживает Pickle из версий Scala 2.x. Scala 2.13.4 поставляется с считывателями TASTy, поэтому поддерживает как все традиционные функции, так и новые, такие как Enums, Intersection types (типы соединения) и другие. Дополнительные сведения см. в руководстве по совместимости.

Заключение

Мы очень рады новой версии нашего основного языка. Она сопровождается таким количеством интересных изменений, хорошо и тщательно продуманных миграций, что невозможно либо не вписать ее в следующий проект, либо не начать рефакторинг уже существующих.

В целом, мне кажется, что Scala 3 это отличная доработка Scala 2. Мы научились жить без многих вещей: для одних это были библиотеки, решавшие проблемы в той или иной степени с ограничениями, для других это было либо невозможно, либо выходило за пределы их зоны комфорта. Поэтому, как только Scala 3 получит широкое распространение, мы ожидаем увидеть более грамотно написанный код, в основном потому, что там это намного проще.


Узнать подробнее о курсе Scala-разработчик.

Смотреть открытый вебинар Эффекты в Scala.

Подробнее..

SQL для аналитики рейтинг прикладных задач с решениями

11.02.2021 12:14:32 | Автор: admin

Привет, Хабр! У кого из вас black belt на sql-ex.ru, признавайтесь? На заре своей карьеры я немало времени провел на этом сайте, практикуясь и оттачивая навыки. Должен отметить, что это было увлекательное и вознаграждающее путешествие. Пришло время воздать должное.

В этой публикации я собрал топ прикладных задач и мои подходы к их решению в терминах SQL. Каждая задача снабжена кусочком данных и кодом, с которым можно интерактивно поиграться на SQL Fiddle.

SQL is intergalactic data speak. SQL - это межгалактический язык данных

- Michael Stonebraker

Моя цель - показать походы и самые распространенные проблемы на понятных и доступных примерах. Конечно, СУБД, на которой решается задача имеет значение. Поддержка функций и синтаксиса варьируется. В SQL Fiddle я задействовал PostgreSQL, Oracle, SQL Server. Для решения серьезных аналитических задач сегодня я чаще всего использую специальные СУБД, такие как Redshift, Vertica, BigQuery, Clickhouse, Snowflake.

Уверен, неискушенные пользователи смогут многое для себя почерпнуть. Продвинутых же пользователей призываю поделиться своими наиболее интересными задачами и поучаствовать в обсуждении.

Конкатенация значений из нескольких строк в одну через разделитель

Когда это может быть полезно? К примеру, если исходный набор данных хранит каждый тег, присвоенный сделке, в отдельной строке (это же получится при соединении таблиц лидов и тегов), и есть необходимость собрать все теги, обойдясь при этом без дублирования строк по каждой сделке.

Формулировка задачи: Для каждого лида вывести список тегов, разделенных запятой в одном столбце

SQL Fiddle (PostgreSQL 9.6): http://sqlfiddle.com/#!17/f3ace/2/0

Входные данные:
Пример решения:
select lead_id ,string_agg(tag, ', ') as tagsfrom leadsgroup by lead_id;
Результат:

Аналитические функции при сохранении всех строк выборки

Речь пойдет о так называемых analytic functions, которые оперируют над партициями данных (окна, windows), возвращая результат для каждой строки. В отличие от aggregate functions, схлопывающих строки, оконные функции оставляют все строки выборки.

Окно определяется спецификацией (выражение OVER) и основывается на трех основных концепциях:

  • Разбиение строк на группы (выражение PARTITION BY)

  • Порядок сортировки строк в каждой группе (выражение ORDER BY)

  • Рамки, которые определяют ограничения по количеству строк относительно каждой строки (выражение ROWS)

Таких функций существует немало, от аналитических: всем известные SUM, AVG, COUNT, менее известные LAG, LEAD, CUMEDIST, и до ранжирующих: RANK, ROWNUMBER, NTILE. Я же приведу несколько простых примеров часто встречающихся запросов:

  • Ко всем транзакциям пользователя вывести дату первой покупки

  • К каждой транзакции добавить дату предыдущей транзакции пользователя

  • Показать сумму покупок пользователя нарастающим итогом

  • Присвоить всем транзакциям пользователя / продавца / отделения порядковый номер

SQL Fiddle: http://sqlfiddle.com/#!17/ee00f/13

Входные данные:
Пример решения:
select  salesid ,dateid ,sellerid ,buyerid ,qty ,first_value(dateid) over (partition by buyerid order by dateid) as first_purchase_dt ,lag(dateid) over (partition by buyerid order by dateid) as previous_purchase_dt ,sum(qty) over (partition by buyerid order by dateid rows between unbounded preceding and current row) as moving_qty ,row_number() over (partition by buyerid order by dateid) as order_numberfrom winsales;
Результат:

Работа с NULL и применение логики ветвления IF-THEN-ELSE в SQL

Про COALESCE / NVL знают все, и нет смысла останавливаться на них подробно. Зато с NVL2 и NULLIF знакомы уже не так много людей.

NULLIF сравнивает два значения и возвращает NULL, если аргументы равны. По сути эта функция - обратна к NVL / COALESCE. Формулировка задачи:

  • Как обработать ошибку деления на 0 (divide by zero error)

  • Как выводить NULL вместо пустых строк ()

SQL Fiddle (PostgreSQL 9.6): http://sqlfiddle.com/#!17/bf56e/2

Входные данные:
Пример решения:
select lead_id ,nullif(tag, '') as tagfrom leads;
Результат:

NVL2 в свою очередь вернет одно из значений, в зависимости от того, является ли входной аргумент NULL или NOT NULL. Например, если в таблице транзакций есть ссылка на invoiceid, значит транзакция в сегменте B2B, и ее следует пометить соответствующим образом.

SQL Fiddle (Oracle 11g R2): http://sqlfiddle.com/#!4/4cac9/11

Входные данные:
Пример решения:
select "transaction_id" ,"ts" ,"invoice_id" ,nvl2("invoice_id", 1, 0) as "is_b2b"from transactions;
Результат:

Но больше всего мне нравится функция DECODE. Она в буквальном смысле позволяет расшифровать значения согласно заданной вами логике:

DECODE ( expression, search, result [, search, result ] [ ,default ] ).

Формулировка задачи: Присвоить численному коду (или, например, битовой маске) текстовые наименования.

SQL Fiddle (Oracle 11g R2): http://sqlfiddle.com/#!4/60341/1

Входные данные:
Пример решения:
select "transaction_id" ,decode("status", 0, 'charge', 1, 'authorize', 2, 'settle', 'void') as "status"from transactions;
Результат:

Опережая вопрос, конечно, эту же логику можно выразить через всем известное выражение CASE. Задача показать что-то интересное, и чем меньше кода - тем красивее, на мой взгляд.

Дедупликация данных

Это классика. Задачу часто спрашивают на собеседованиях в формулировке как удалить дубли / копии строк, и решить ее можно несколькими способами. Я привык мыслить в терминах историзации данных в Хранилище, и удаление мне ни к чему, поэтому для решения задачи я воспользуюсь ранжирующей функцией ROWNUMBER().

Формулировка задачи: Выбрать самую актуальную запись с учетом статуса (успешная / отмененная транзакция) и временнОй метки

SQL Fiddle (Oracle 11g R2): http://sqlfiddle.com/#!4/ad305/1

Входные данные:
Пример решения:
with decoded as (   select   "transaction_id"   ,"is_successful"   ,"ts"   ,decode("is_successful", 'true', 0, 'false', 1, 2) as "order_is_successful"   from transactions),ordered as (   select   "transaction_id"   ,"is_successful"   ,"ts"   ,row_number() over(partition by "transaction_id" order by "order_is_successful" asc, "ts" desc) as rn   from decoded)select "transaction_id" ,"is_successful" ,"ts"from orderedwhere rn = 1;
Результат:

Некоторые СУБД, например, Teradata позволяют сделать запрос короче при помощи выражения QUALIFY:

select *from students_db.exam_resultsqualify row_number() over (partition by subject order by marks desc) = 1;

Анализ временных рядов

Просто не могу обойти это стороной. ВременнАя шкала - это, безусловно, одно из наиболее часто используемых измерений. Отчетность зачастую строится вокруг измерения метрик и их динамики относительно периодов: неделя, месяц, время суток и т.д.

Замечательно, если ваша BI система умеет работать с различными абсолютными и относительными фреймами, и наружу выставляет красивый визуальный интерфейс. Еще лучше, если в ваш инструментарий аналитика входит пара наиболее используемых функций:

  • Получение текущей даты (+ время) - CURRENTDATE, CURRENTTIMESTAMP

  • Разница между событием и текущим временем - DATEDIFF

  • Подсчет времени истечения срока действия события - DATEADD

  • Дата начала недели, в которой произошло событие - DATETRUNC

  • Конвертация Unix Timestamp (epoch) в человекочитаемый формат

SQL Fiddle (MS SQL Server 2017): http://sqlfiddle.com/#!18/618cf/6

Входные данные:
Пример решения:
select ts ,_metadata_ts_epoch ,convert(date, getdate()) as current_dt ,current_timestamp as current_ts ,datediff(minute, ts, getdate()) as minutes_since_ts ,dateadd(hour, 36, ts) as ts_expiration_ts  ,dateadd(week, datediff(week, 0, ts), 0) as ts_week ,dateadd(S, (_metadata_ts_epoch / 1000), '1970-01-01') as _metadata_tsfrom transactions;
Результат:

Анализ истории со Slowly Changing Dimensions (SCD)

В основе Хранилища Данных лежит принцип историзации. Иначе говоря - это возможность получить состояние той или иной сущности на определенный момент времени, а также проследить цепочку событий и изменений атрибутов и показателей. Существует несколько способов организации хранения истории. Один из наиболее популярных подходов - запись новой строки на любое изменение атрибутного состава, с указанием даты начала и окончания действия каждой строки.Есть несколько задач, с которыми вы с большой долей вероятности можете встретиться.

Формулировка задачи: Какой статус был у клиентов на 3-й день месяца?

SQL Fiddle (PostgreSQL 9.6): http://sqlfiddle.com/#!17/743e9/6

Входные данные:
Пример решения:
select  client_id  ,statusfrom clientswhere '2021-02-03' >= valid_from and '2021-02-03' < coalesce(valid_to, '2100-01-01');
Результат:

Формулировка задачи: Как в течение недели росло количество активных клиентов?

SQL Fiddle (PostgreSQL 9.6): http://sqlfiddle.com/#!17/743e9/11

Пример решения:
select   c.dt  ,h.status  ,count(distinct h.client_id)from calendar c  left join clients h      on c.dt >= valid_from and c.dt < coalesce(valid_to, '2100-01-01')::datewhere trueand c.dt between '2021-02-01' and '2021-02-07'and h.status in ('active')group by   c.dt  ,h.statusorder by 1, 3 desc;
Результат:

С помощью такого подхода можно подсчитать долю неактивных контрагентов на каждую дату за последний месяц. При этом неактивным считается контрагент, не совершивший ни одной транзакции за предыдущие 7 дней на каждую дату. Вот так может выглядеть визуализация решения задачи на дашборде:

Использование выражения CASE в агрегирующих функциях

Агрегирующие функции могут принимать в качестве аргумента результат оценки выражения CASE. Таким образом можно к агрегируемым строкам применить псевдофильтр. Это напоминает мне использование формулы СУММЕСЛИ из старого доброго Excel, только для реляционных баз данных. Смотрите сами:

  • Подсчитать все лиды и выручку

  • Подсчитать количество лидов со статусом success

  • Подсчитать выручку лидов с тегом python

SQL Fiddle (MS SQL Server 2017): http://sqlfiddle.com/#!18/dc01d5/4

Входные данные:
Пример решения:
select dt ,count(1) as leads_total ,sum(case status when 'success' then 1 else 0 end) as leads_success ,sum(case when tags like '%python%' then 1 else 0 end) as leads_python ,sum(amount) as amount_total ,sum(case status when 'success' then amount else 0 end) as amount_successfrom leadsgroup by dtorder by dt;
Результат:

Парсинг колонки с разделением на отдельные атрибуты

Чаще всего так поступают в условиях внешних ограничений, когда иного выхода нет. Например, при ограниченном наборе полей в CRM системе. Или при передаче нескольких UTM-меток в одной строковой переменной. Еще так могут делать люди, которые не слышали про нормализацию данных.

Формулировка задачи: Выделить закодированные в названии кампании атрибуты в отдельные колонки: сеть, регион, категория, температура, бренд.

SQL Fiddle (PostgreSQL 9.6): http://sqlfiddle.com/#!17/205e7b/6

Входные данные:
Пример решения:
select campaign ,split_part(campaign, '-', 1) as network ,split_part(campaign, '-', 2) as region ,split_part(campaign, '-', 3) as category  ,nullif(split_part(campaign, '-', 4), 'None') as temperature  ,split_part(campaign, '-', 5) as brand from campagins;
Результат:

Чуть более сложная ситуация с парсингом UTM-меток, а именно UTMContent, которая по сути является контейнером для произвольного набора атрибутов, разделенных любым символом. Поэтому стоит быть последовательным и аккуратным при формировании таких меток, хотя зачастую инженер вынужден работать с тем, что есть.

Формулировка задачи: Разбить строку UTMContent на отдельные атрибуты cid, gid, aid, kwd с соблюдением соответствия ключ-значение. Каждое значение предваряется наименованием ключа, все значения разделены вертикальной строкой (|).

SQL Fiddle (PostgreSQL 9.6): http://sqlfiddle.com/#!17/4f65e/4

Входные данные:
Пример решения:
select substring("UTMContent" from '%cid_#"%#"_gid%' FOR '#' ) AS cid ,substring("UTMContent" from '%gid_#"%#"_aid%' FOR '#' ) AS gid ,substring("UTMContent" from '%aid_#"%#"_dvc%' FOR '#' ) AS aid ,substring("UTMContent" from '%kwd_#"%#"_pos%' FOR '#' ) AS kwdfrom utm;
Результат:

FULL JOIN для соединений без потери строк

Уверен, что все знают про FULL JOIN, но кто хоть иногда использует этот тип соединения? Это незаменимый подход в ситуациях, когда я хочу сохранить все исходные строки с каждой стороны джоина. Иначе говоря, недопустимо терять факты трат денежных средств, даже если для них не нашлось соответствующих лидов в таблицах CRM.

А теперь представьте ситуацию, когда таблиц больше двух. Это может быть веб-аналитика, выгрузки из рекламных кабинетов, CRM. В этом случае я дополнительно формирую мета-колонки isrowmatched (нашлось ли совпадение - да / нет) и roworigin (источник данных для конкретной строки).

Формулировка задачи: Подготовить витрину-трекер для сквозной аналитики лидов из CRM и трат из Рекламных Кабинетов (Яндекс.Директ, Google Adwords, Facebook).

SQL Fiddle (PostgreSQL 9.6): http://sqlfiddle.com/#!17/227eaf/1

Входные данные:
Пример решения:
select coalesce(c.hash_key, l.hash_key) as hash_key ,coalesce(c.dt, l.dt) as dt ,coalesce(c.campaign_id, l.campaign_id) as campaign_id -- costs ,coalesce(c.platform, null) as platform ,coalesce(c.clicks, 0) as clicks ,coalesce(c.costs, 0) as costs -- leads ,coalesce(l.leads, 0) as leads ,coalesce(l.amount, 0) as amount -- meta ,case     when c.dt is not null then c.platform     when l.dt is not null then 'crm'   end as meta_row_origin ,case     when c.hash_key = l.hash_key then 1     else 0   end as meta_is_row_matchfrom costs as c   full join leads as l on l.hash_key = c.hash_key;
Результат:

Пример упрощен и умозрителен. Однако этой задаче я посвятил одну из своих предыдущих публикаций: Сквозная Аналитика на Azure SQL + dbt + Github Actions + Metabase и недавнее выступление на вебинаре: Путь Инженера Аналитики: Решение для Маркетинга. Тема заслуживает отдельного внимания.

Разбиение пользовательских событий на сессии

Сессионизация - весьма интересная и сложная задача, сочетающая в себе сразу комплекс инженерных и аналитических решений. С ростом популярности и востребованности всевозможных трекеров, таких как Google Analytics, Snowplow, Amplutide кратно возрастает спрос на решение подобного рода задач.

Для чего это можно использовать? Прежде всего, для того, чтобы перейти от анализа хитов (кликов) к полноценному анализу пользовательского взаимодействия и поведения. Во-вторых, улучшение UX и качества сервисов, проведение A/B тестирования. Наконец, поиск паттернов, определенных сегментов пользователей, в том числе fraud monitoring (защита от мошенничества и ботов).

Чуть подробнее про дефиницию сессии от Google Analytics: How a web session is defined in Universal Analytics. Резюмируя, сессия - это набор пользовательских действий в рамках заданного промежутка времени. Сессия завершается при следующих событиях:

  • 30 минут бездействия

  • Начало новых суток

  • Смена источника трафика (возврат на сайт по клику на новый рекламный баннер)

Базовая задача сессионизации сводится к следующему: превратить последовательность кликов из лога веб-сервера в набор сессий.

SQL Fiddle (PostgreSQL 9.6): http://sqlfiddle.com/#!17/17271/3

Попробуем декомпозировать и решить задачу по частям:

  • Шаг 1. Для каждого пользователя берем идентификатор просмотра, время просмотра, источник трафика (хеш-сумма). Хеш-сумма берется от текстовой конкатенации атрибутов источника трафика: utm_source + utm_medium + utm_campaign. При этом обрабатываются null-значения в любом из столбцов (заменяются на литерал 'null'). По хеш-сумме легко проверить смену источника трафика.

Шаг 1
 select   user_id   ,hit_id   ,ts   ,md5(concat(coalesce(utm_source, 'null'), coalesce(utm_medium, 'null'), coalesce(utm_campaign, 'null'))) as utm_hash from hits_raw
  • Шаг 2. Для каждого хита выводим предыдущий хит и соответствующее ему время. Окно - по пользователю, сортировка по времени хита:

Шаг 2
select   user_id   ,hit_id   ,ts   ,lag(ts, 1) over (partition by user_id order by ts) as lag_ts   ,utm_hash   ,lag(utm_hash, 1) over (partition by user_id order by ts) as lag_utm_hash from hits
  • Шаг 3. Рассчитываем, является ли каждый хит началом новой сессии. Это проверка на выполнение любого из трех указанных выше условий окончания сессии:

Шаг 3
select   user_id   ,hit_id   ,ts   ,lag_ts   ,case     when utm_hash <> lag_utm_hash then 1     when date_part('day', ts - lag_ts) <> 0 then 1     when date_part('hour', ts - lag_ts) * 60 +             date_part('minute', ts - lag_ts) > 30 then 1     else 0    end as is_new_session --    ,date_part('day', ts - lag_ts) as days_diff--    ,date_part('hour', ts - lag_ts) * 60 +--              date_part('minute', ts - lag_ts) as minutes_diff   ,utm_hash   ,lag_utm_hash from lags
  • Шаг 4. Присваиваем каждой сессии уникальный идентификатор. Для этого сначала необходимо пронумеровать сессии одного пользователя монотонно возрастающими числами. Затем построить уникальный суррогатный ключ сессии: к номеру сессии добавить идентификатор пользователя, взять хеш-сумму:

Шаг 4
select   user_id   ,hit_id   ,ts   ,is_new_session   ,sum(is_new_session) over (partition by user_id order by ts rows between unbounded preceding and current row) as session_index   ,md5(concat(user_id, sum(is_new_session) over (partition by user_id order by ts rows between unbounded preceding and current row))) as session_id from new_sessions
Результат:

В реальном мире всё сложнее

Помимо логики, выраженной в SQL, не меньшее значение имеет ряд других факторов:

  • СУБД, с которой вы работаете: то, какие функции и возможности она поддерживает, формат хранения данных: в виде колонок или строк

  • Фактически используемый план выполнения запроса: алгоритмы соединения таблиц, локальность операций, наличие статистических данных у оптимизатора

  • Используемые физические и логические модели данных: индексы, материализованные представления, кеш, предварительно отсортированные данные


На занятиях курса Data Engineer я и мои коллеги готовим объемлющий и интересный контент, затрагивающий множество тем, связанных с архитектурой аналитических приложений, внутренним устройством систем обработки больших данных и развертыванием ML.

Советую посетить ближайшие открытые вебинары:

Оставляйте ваши комментарии и вопросы, предлагайте собственные примеры задач и подходы к решению.

Следить за моими публикациями в авторском канале: https://t.me/enthusiastech

Благодарю за внимание.

Подробнее..

Перевод Разворачиваем кластер Kubernetes с помощью Kubernetes

17.02.2021 20:19:33 | Автор: admin

В рамках курса DevOps практики и инструменты подготовили для вас перевод полезной статьи.

Также приглашаем на открытый вебинар по теме
Prometheus: быстрый старт. На вебинаре участники вместе с экспертом рассмотрят архитектуру Prometheus и как она работает с метриками; разберут, как формировать алерты и события в системе.


Подождите что, что? Да, я уже слышал подобную реакцию на мое предложение использовать Kubernetes для построения кластеров Kubernetes.

Но для автоматизации облачной инфраструктуры мне не приходит на ум ничего лучше, чем сам Kubernetes. Используя один центральный кластер K8s, мы создаем и управляем сотнями других кластеров K8s. В этой статье я покажу вам, как это сделать.

Примечание: SAP Concur использует AWS EKS, но концепции, рассматриваемые здесь, применимы и к Google GKE, Azure AKS и к любому другому облачному провайдеру, предлагающему Kubernetes.

Готовность к промышленной эксплуатации

Развернуть кластер Kubernetes в любом из основных облачных провайдеров очень легко. Создать и запустить кластер в AWS EKS можно одной командой:

$ eksctl create cluster

Однако для создания кластера Kubernetes, готового к промышленной эксплуатации (production ready), требуется нечто большее. Хотя готовность к промышленной эксплуатации каждый понимает по своему, SAP Concur использует следующие четыре этапа создания и поставки кластеров Kubernetes.

Четыре этапа сборки

  • Предварительные тесты. Набор базовых тестов целевого окружения AWS, проверяющих выполнение всех требований перед непосредственным созданием кластера. Например: доступные IP-адреса для каждой подсети, AWS exports, параметры SSM и другие переменные.

  • EKS control plane и nodegroup. Фактическая сборка кластера AWS EKS с подключением рабочих нод.

  • Установка дополнений. Это то, что сделает ваш кластер более милым :-) Установите такие дополнения как Istio, logging integration, autoscaler и т.д. Этот список дополнений не является исчерпывающим и совершенно необязателен.

  • Валидация кластера. На этом этапе мы проверяем кластер (основные компоненты EKS и дополнения) с функциональной точки зрения перед передачей его в эксплуатацию. Чем больше тестов вы напишете, тем крепче будете спать. (Особенно если вы дежурный в техподдержке!)

Склеиваем этапы вместе

На каждом из этих четырех этапов используются разные инструменты и приемы, о которых я расскажу позже. Мы искали единый инструмент для всех этапов, который склеил бы все вместе, поддерживал последовательное и параллельное выполнение, был событийно-ориентированным и, желательно, с визуализацией сборки.

И мы нашли Argo. В частности, Argo Events и Argo Workflows. Оба они запускаются в Kubernetes как CRD и используют декларативный YAML, который используется и в других деплоях Kubernetes.

Мы нашли идеальную комбинацию: императивная оркестрация (Imperative Orchestration), декларативная автоматизация (Declarative Automation).

Готовый к продакшену кластер K8s, построенный с помощью Argo WorkflowsГотовый к продакшену кластер K8s, построенный с помощью Argo Workflows

Argo Workflows

Argo Workflows это container-native workflow engine с открытым исходным кодом для оркестрации параллельных заданий в Kubernetes. Argo Workflows реализован как Kubernetes CRD.

Примечание: Если вы знакомы с K8s YAML, то обещаю, что для вас все будет просто.

Давайте посмотрим, как вышеуказанные этапы сборки могут выглядеть в Argo Workflows.

1. Предварительные тесты

Предварительные тесты выполняются параллельно, с повторными попытками в случае сбояПредварительные тесты выполняются параллельно, с повторными попытками в случае сбоя

Для написания тестов мы используем фреймворк BATS. Написать тест в BATS очень просто:

#!/usr/bin/env bats@test More than 100 available IP addresses in subnet MySubnet {AvailableIpAddressCount=$(aws ec2 describe-subnets --subnet-ids MySubnet | jq -r .Subnets[0].AvailableIpAddressCount) [ ${AvailableIpAddressCount} -gt 100 ]}

Параллельный запуск BATS-тестов (указанного выше теста avail-ip-addresses.bats и еще трех) с использованием Argo Workflow может выглядеть следующим образом:

 name: preflight-tests  templateRef:     name: argo-templates    template: generic-template  arguments:    parameters:     name: command      value: {{item}}  withItems:   bats /tests/preflight/accnt-name-export.bats   bats /tests/preflight/avail-ip-addresses.bats   bats /tests/preflight/dhcp.bats   bats /tests/preflight/subnet-export.bats

2. EKS control plane и nodegroup

EKS control plane и nodegroup с зависимостямиEKS control plane и nodegroup с зависимостями

Для создания кластера EKS вы можете выбрать различные инструменты. Доступны eksctl, CloudFormation или Terraform. Построение EKS в два этапа с зависимостями, используя шаблоны CloudFormation (eks-controlplane.yaml и eks-nodegroup.yaml), в Argo Workflow может выглядеть следующим образом.

 name: eks-controlplane  dependencies: [preflight-tests]  templateRef:     name: argo-templates    template: generic-template arguments:   parameters:    name: command     value: |       aws cloudformation deploy \       --stack-name {{workflow.parameters.CLUSTER_NAME}} \       --template-file /eks-core/eks-controlplane.yaml \       --capabilities CAPABILITY_IAM- name: eks-nodegroup  dependencies: [eks-controlplane]  templateRef:     name: argo-templates    template: generic-template  arguments:    parameters:     name: command      value: |        aws cloudformation deploy \        --stack-name {{workflow.parameters.CLUSTER_NAME}}-nodegroup \        --template-file /eks-core/eks-nodegroup.yaml \        --capabilities CAPABILITY_IAM

3. Установка дополнений

Параллельная установка дополнений с зависимостямиПараллельная установка дополнений с зависимостями

Установить дополнения можно, используя kubectl, helm, kustomize или их комбинацию. Например, установка metrics-server с helm template и kubectl, при условии, что требуется установка metrics-server, в Argo Workflows может выглядеть следующим образом.

 name: metrics-server  dependencies: [eks-nodegroup]  templateRef:     name: argo-templates    template: generic-template  when: {{workflow.parameters.METRICS-SERVER}} != none  arguments:    parameters:     name: command      value: |        helm template /addons/{{workflow.parameters.METRICS-SERVER}}/ \        --name metrics-server \        --namespace kube-system \        --set global.registry={{workflow.parameters.CONTAINER_HUB}} | \        kubectl apply -f -

4. Валидация кластера

Параллельная валидация кластера с повторными попытками при ошибках.Параллельная валидация кластера с повторными попытками при ошибках.

Для проверки функциональности дополнений мы используем великолепную BATS-библиотеку DETIK, которая упрощает написание K8s-тестов.

#!/usr/bin/env batsload lib/utilsload lib/detikDETIK_CLIENT_NAME=kubectlDETIK_CLIENT_NAMESPACE="kube-system"@test verify the deployment metrics-server {  run verify there are 2 pods named metrics-server [ $status -eq 0 ]  run verify there is 1 service named metrics-server [ $status -eq 0 ]  run try at most 5 times every 30s to find 2 pods named metrics-server with status being running [ $status -eq 0 ]  run try at most 5 times every 30s to get pods named metrics-server and verify that status is running [ $status -eq 0 ]}

Выполнение указанного выше тестового файла BATS DETIK (metrics-server.bats), при условии, что metrics-server установлен, в Argo Workflows может выглядеть так:

 name: test-metrics-server  dependencies: [metrics-server]  templateRef:    name: worker-containers    template: addons-tests-template  when: {{workflow.parameters.METRICS-SERVER}} != none  arguments:    parameters:     name: command      value: |        bats /addons/test/metrics-server.bats

Представьте, сколько тестов вы можете сюда подключить. Вы можете запустить Sonobuoy conformance tests, Popeye A Kubernetes Cluster Sanitizer и Fairwinds Polaris. Подключайте их с помощью Argo Workflows!

Если вы дошли до этого момента, то значит, у вас есть полностью рабочий AWS EKS кластер, готовый к промышленной эксплуатации, с установленным, протестированным и готовым metrics-server. Вы молодцы!

Но мы еще не прощаемся, самое интересное я оставил на конец.

WorkflowTemplate

Argo Workflows поддерживает шаблоны (WorkflowTemplate), что позволяет создавать повторно используемые workflow. Каждый из четырех этапов сборки это шаблон. По сути, мы создали строительные блоки, которые можно комбинировать по мере необходимости. Используя один главный workflow, можно выполнять все этапы сборки по порядку (как в примере выше), или независимо друг от друга. Такая гибкость достигается с помощью Argo Events.

Argo Events

Argo Events это управляемый событиями фреймворк автоматизации рабочих процессов для Kubernetes (workflow automation framework), который помогает запускать объекты K8s, рабочие процессы Argo Workflow, бессерверные рабочие нагрузки и др. по событиям из различных источников, таких как webhook, s3, расписания, очереди сообщений, gcp pubsub, sns, sqs и т.д.

Сборка кластера запускается вызовом API (Argo Events) с использованием JSON. Кроме того, каждый из четырех этапов сборки (WorkflowTemplate) имеет собственную конечную точку API. Персонал, сопровождающий Kubernetes, может извлечь из этого большую пользу:

  • Не уверены в состоянии облачной среды? Используйте API предварительных тестов.

  • Хотите построить голый EKS-кластер? Используйте eks-core (control-plane и nodegroup) API.

  • Хотите установить или переустановить дополнения в существующем EKS-кластере? Есть addons API.

  • С кластером происходит что-то странное и вам нужно быстро запустить тесты? Вызовите test API.

Возможности Argo

Как Argo Events, так и Argo Workflows включают в себя большой набор возможностей, которые вам не потребуется реализовывать самостоятельно.

Вот семь из них, которые наиболее важны для нас:

  • Параллелизм

  • Зависимости

  • Повторные попытки обратите внимание на скриншотах выше на красные предварительные и валидационные тесты. Argo автоматически повторил их с последующим успешным завершением.

  • Условия

  • Поддержка S3

  • Шаблоны рабочих процессов (WorkflowTemplate)

  • Параметры Events Sensor

Заключение

Мы получили возможность использовать различные инструменты, которые работают совместно и определяют желаемое состояние инфраструктуры, обеспечивая гибкость и высокую скорость проекта. Мы планируем использовать Argo Events, Argo Workflows и для других задач автоматизации. Возможности безграничны.


Узнать больше о курсе DevOps практики и инструменты.

Смотреть открытый вебинар по теме Prometheus: быстрый старт.

Подробнее..

Перевод Argo CD готов к труду и обороне в Kubernetes

26.02.2021 20:18:58 | Автор: admin

Привет, Хабр. В рамках курса Инфраструктурная платформа на основе Kubernetes подготовили для вас перевод полезного материала.

Также приглашаем на открытый вебинар Работа с NoSQL базами в k8s (на примере Apache Cassandra). На вебинаре участники вместе с экспертом рассмотрят плюсы и минусы запуска Apache Cassandra в k8s: насколько такой вариант установки готов к продакшену, и какие подводные камни имеются.


В этой статье мы рассмотрим несколько вопросов касательно Argo CD: что это такое, зачем его используют, как его развернуть (в Kubernetes), как его использовать для реализации непрерывного развертывания (continuous deployment), как настроить SSO с помощью GitHub и разрешений и т. д.

Что такое Argo CD и GitOps

Argo CD это декларативный GitOps-инструмент непрерывной доставки (continuous delivery) для Kubernetes.

Но что же такое GitOps?

Официальное определение гласит, что GitOps это способ реализации непрерывного развертывания (continuous deployment) облачных приложений. Он фокусируется на создании ориентированного на разработчиков опыта эксплуатации инфраструктуры с использованием инструментов, с которыми разработчики уже знакомы, включая Git и Continuous Deployment.

Официальное определение не вдается в подробности, не так ли? Возможно, вы не так часто слышали об этой концепции, но, скорее всего, вы уже использовали ее: вы определяете свои K8s ресурсы в YAML или с помощью Helm-диаграммы, вы используете Git в качестве единого источника истины (single source of truth), вы запускаете одни автоматизированные CI задачи для деплоя в продакшн, когда ваша ветвь master изменена, и вы запускаете другие задачи по пул реквесту для деплоя на стейджи.

Почему GitOps

Определения, конфигурации и среды приложений должны быть декларативными и подлежать контролю версий.

Развертывание (деплой) приложений и управление жизненным циклом должны быть:

  • автоматизированными

  • проверяемыми

  • простыми для понимания

Эти правила не требуют пояснений, поскольку они приносят очевидную пользу. Вам не хотелось бы тратить много времени на выяснение, какая версия развернута в какой среде и какой тег git ей соответствует; вам нужна возможность отката до определенной версии; вы хотели бы, чтобы определение приложений и деплоев было легко читаемым для людей; вы хотите, чтобы все было автоматизировано и не исключало возможность проверки.

В этом и заключается концепция GitOps и то, почему он хорош.

Почему Argo CD

Можно ли достичь вышеупомянутых преимуществ GitOps, используя любые другие инструменты CI/CD? Скорее всего, да. Например, вы можете использовать старый добрый Jenkins, определить разные задачи для разных ветвей, после чего задачи будут следить за Git-репозиториями или использовать хуки для реакции на события, а в конвейере вы можете выполнить несколько git clone и helm install. Нет ничего плохого в Jenkins, на что я указывал в моей предыдущей статье о CI: Введение в CI: сравнение 17 основных инструментов CI или Как выбрать лучшее CI в 2020 году.

Если вы не читали ее, я рекомендую вам сделать это, чтобы лучше понять, что мы ищем при выборе инструмента.

По сути, Argo CD упрощает все, о чем я упоминал выше: легко настраивать, легко развертывать, почти без особых напрягов, так что вы можете сосредоточиться на более важных вещах, а не возиться с инструментом, просто чтобы понять, как его использовать и настраивать.

Все еще неубедительно

Хорошо, я понимаю, что у вас есть опасения по поводу внедрения новых инструментов, поэтому вот несколько фактов, которые могут вас убедить:

Это часть фонда Cloud Native Computing Foundation (CNCF).

Он активно поддерживается и постоянно улучшается. Посмотрите количество на коммитов:

Мне порой доставляет удовольствие покопаться в git-репозиториях в поисках разнообразной информации, и вот что я нашел здесь:

  • первый релиз v0.1.0 состоялся в марте 2018 года (относительно новый проект)

  • v1.0.0 зарелижен в мае 2019 (быстро развивается)

  • на момент написания статьи имеет версию v1.7.8 (ноябрь 2020, развивается очень быстро)

  • 4,3 тыс. звезд в репозитории Argo CD (еще больше на других репозиториях того же проекта)

  • 60 открытых PR и 500 открытых задач на сегодняшний день (очень даже неплохо, это означает, что сообщество активно занимается им и постоянно исправляет и придумывает запросы на новый функционал)

Он также упоминается в техническом радаре CNCF:

Он все еще находится на стадии ОЦЕНКА (ASSESS), что означает, что члены CNCF протестировали его, и он показался многообещающим. Он рекомендован к рассмотрению, когда вы сталкиваетесь с конкретной потребностью в подобной технологии в вашем проекте.

Радар является инициативой CNCF End User Community. Это группа из более чем 140 ведущих компаний и стартапов, которые регулярно встречаются для обсуждения проблем и передовых методов внедрения облачных технологий. Если вам лень разбираться, какой инструмент использовать, или если вы чувствуете себя неуверенно в отношении новых вещей, которые вы еще не пробовали, довольно безопасно выбрать один из вариантов, которые предлагает радар, потому что многие крупные компании уже протестировали его за вас, множество из имен которых вы уже слышали. Если это подходит им, есть большая вероятность, что и вам это понравится.

Развертывание

Окей, приступим к практике. У вас должен быть запущен кластер K8s, потому что мы собираемся делать это внутри k8s.

kubectl create namespace argocdkubectl apply -n argocd -f \https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml 

Один YAML, чтоб править всеми.

После развертывания у вас будет несколько запущенных подов и сервисов. Примеры:

Pods:argocd-application-controller-6b47c9bd78-kp6djargocd-dex-server-7b6d8776d8-knsxxargocd-redis-99fb49846-l466kargocd-repo-server-b664bd94b-bmtwrargocd-server-768879948c-sx875Services:argocd-dex-serverargocd-metricsargocd-redisargocd-repo-serverargocd-serverargocd-server-metrics

Комментарии по доступу к сервису и ingress:

Обратите внимание, что в развертывании по умолчанию сервис работает как тип Cluster IP, и ingress по умолчанию нет. Поэтому, если вы хотите получить доступ к сервису, вам нужно будет либо выполнить переадресацию портов, либо изменить тип сервиса на балансировщик нагрузки, либо создать ingress.

Если вы делаете это в производственном кластере, например в EKS в AWS, скорее всего, вы хотите использовать ingress и, вероятно, у вас уже есть ingress-контроллер. Вход для Argo CD здесь немного сложен, потому что на порту 443 он имеет как HTTPS (для веб-интерфейса консоли), так и GRPC для вызовов API командной строки. Если вы используете EKS, например, с ingress-контроллером Nginx, скорее всего, вы уже выполнили завершение TLS там, поэтому вам может потребоваться несколько ingress-объектов и хостов, один для протокола HTTP, другой для GRPC. Подробнее смотрите здесь.

Установка CLI

Для Mac это всего лишь одна команда:

brew install argocd

Инициализация

После установки изначальный пароль совпадает с именем пода сервера. Мы можем войти в систему:

argocd login  (переадресация портов, служба балансировки нагрузки, ingress - на ваш выбор)

и изменить пароль:

argocd account update-password

Вы также можете войти в консоль пользовательского интерфейса со своим юзернеймом и паролем, чтобы прочувствовать:

Страница входа в пользовательский интерфейс Argo CD

Добавление кластеров

Вы можете управлять сразу несколькими кластерами внутри Argo CD, например, у вас могут быть разные кластеры для разных сред, таких как dev, test, staging, production или что-то еще.

По умолчанию кластер, в котором развернут Argo CD, уже настроен с помощью Argo CD:

Вы также можете увидеть список кластеров с помощью интерфейса командной строки:

argocd cluster list

Если вы хотите управлять другими кластерами, вы можете запустить:

argocd cluster add CONTEXTNAMECONTEXTNAME- имя kube контекста в вашей локальной конфигурации.

Helloworld-пример развертывания

Теперь мы можем попробовать создать приложение на Argo CD.

Версия TL;DR или версия Мне не нравится UI в этом разделе это одна команда:

argocd app create helloworld --repo https://github.com/ironcore864/go-hello-http.git --path helm --sync-policy automatic --dest-server https://kubernetes.default.svc --dest-namespace default --values values.yaml --values values.dev.yaml

Эта CLI-команда создаст приложение, развернет его и синхронизирует. Чтобы продемонстрировать простоту Argo CD, я буду делать то же самое в пользовательском интерфейсе, а именно:

Нажмите кнопку NEW APP в консоли пользовательского интерфейса:

Затем вам нужно ввести несколько параметров для задачи, например:

  • Application Name: имя этого приложения. Здесь я назову его просто helloworld

  • Project: вы можете выбрать default. Project (проект) это концепция внутри Argo CD, в рамках которой вы можете создать несколько приложений и связать каждое приложение с проектом

  • Sync policy: вы можете выбрать между Manual и Automatic (что даст вам настоящий GitOps). Здесь я выберу Automatic (обратите внимание, что здесь в пользовательском интерфейсе значение по умолчанию Manual).

Нажмите SYNC POLICY и выберите Automatic

Затем мы перейдем к SOURCE части. Нам нужно указать URL-адрес git. Я собираюсь использовать пример, расположенный по адресу. Если вы перейдете в этот репозиторий, вы обнаружите, что там нет ничего, кроме простого Golang приложения с папкой с именем helm, которая представляет собой диаграмму для развертывания с несколькими файлами значений.

После того, как вы ввели URL-адрес git-репозитория, кликните часть PATH, и вы обнаружите, что Argo CD уже автоматически обнаружил, что у нас есть папка helm в этом репозитории, которая содержит вещи, которые могут нас заинтересовать:

Кликните Path и выберите имя папки helm в раскрывающемся меню.

Итак, здесь мы просто кликаем раздел Path и выбираем папку helm.

Стоит отметить, что Argo CD поддерживает несколько инструментов для развертывания. Сам Argo CD не предвзят; он позволяет использовать собственный YAML k8s, или kustomize, или helm. Например, если файлы в Path представляют собой схему управления, Argo CD знает, что нужно запустить установку Helm; но если это просто файлы YAML k8s, Argo CD знает, что вместо этого нужно запустить kubectl apply. Умно, не правда ли?

В разделе Destination нам нужно выбрать, в каком кластере Kubernetes развернуть это приложение (выбор из раскрывающегося списка) и в каком пространстве имен (введите текст).

Щелкните URL-адрес кластера, выберите кластер для развертывания и введите пространство имен.

Поскольку в этом примере в нашей папке helm есть диаграмма, Argo CD автоматически загружает новый раздел с именем Helm, чтобы попросить вас выбрать, какой файл значений, который нужно применить:

Кликните раздел VALUES FILES, и вы можете выбрать один или несколько файлов из списка, который выбирается из Path, настроенного ранее.

Это раскрывающееся меню, вам даже не нужно вводить имена файлов. Эта часть хорошо интегрирована с helm причина, по которой я предпочитаю создавать приложение в пользовательском интерфейсе, хотя то же самое можно сделать с помощью одной единственной CLI-команды.

Здесь мы указали два файл со значениями, один по умолчанию, другой для dev стейджа, и в нем есть значения, которые переопределяют значения, определенные по умолчанию.

После нажатия кнопки Create Argo CD синхронизирует (sync) статус, определенный в git-репозитории, и если текущее состояние не совпадает с тем, что определено в git, Argo CD синхронизирует его и переведет состояние в то же, что определено в git. Через некоторое время вы увидите, что приложение синхронизировано, и какие компоненты развернуты:

Приложение синхронизированоПриложение синхронизированоПодробное представление приложенияПодробное представление приложения

В этом примере у нас есть развертывание, в котором развернут только 1 под (значения из values.dev.yaml переопределяет 3 пода по умолчанию, определенные в файле values.yaml), и сервис, раскрывающий развертывание. Теперь, если мы перейдем к целевому кластеру и проверим, он действительно уже развернут:

Приложение действительно развернуто, без шутокПриложение действительно развернуто, без шуток

Это демонстрирует весь процесс, в котором вы создаете приложение и развертываете его. Весь процесс занимает около 1 минуты, без написания bash-скриптов для cd в какую-либо папку, а затем для установки helm, или, что еще хуже, если у вас нет подходящего образа с helm, необходимости его собрать или найти.

Там также есть много CLI-команд, которые можно использовать, если вы предпочитаете их пользовательскому интерфейсу, чтобы у вас был контроль над всем, не выходя из зоны комфорта.

GitHub SSO

Последняя, но не по важности, тема это пользователи и разрешения, как и в каждом новом инструменте, который мы хотим внедрить в нашу команду.

После установки Argo CD имеет одного встроенного администратора, который имеет полный доступ к системе. Рекомендуется использовать пользователя с правами администратора только для изначальной настройки, а затем переключиться на локальных пользователей или настроить SSO-интеграцию.

Вы можете создать локального пользователя в Argo CD, но вы, вероятно, захотите настроить SSO.

Argo CD включает и поставляет Dex как часть установочного комплекта с целью делегирования аутентификации внешнему поставщику идентификации. Поддерживаются несколько типов поставщиков идентификации (OIDC, SAML, LDAP, GitHub и т. Д.). Для настройки единого входа (SSO) на Argo CD необходимо отредактировать файл конфигурации argocd-cm с настройками Dex-коннектора. После регистрации нового OAuth приложения в git вы можете отредактировать configmap argocd-cm, чтобы добавить следующие значения:

data:  url: https://argocd.example.com  dex.config: |    connectors:      # GitHub example      - type: github        id: github        name: GitHub        config:          clientID: aabbccddeeff00112233          clientSecret: $dex.github.clientSecret          orgs:          - name: your-github-org      # GitHub enterprise example      - type: github        id: acme-github        name: Acme GitHub        config:          hostName: github.acme.com          clientID: abcdefghijklmnopqrst          clientSecret: $dex.acme.clientSecret          orgs:          - name: your-github-org

После сохранения изменения должны вступить в силу автоматически, и у вас будет GitHub SSO уже для всей вашей команды.

GitHub SSO (здесь в примере корпоративный git)GitHub SSO (здесь в примере корпоративный git)

Если вы запустите GitHub SSO с новым пользователем, входящим в систему, он не увидит список кластеров или только что созданное приложение это из-за функций RBAC, позволяющих ограничивать доступ к ресурсам Argo CD. После того, как мы уже включили SSO, мы можем настроить RBAC, создав следующую configmap argocd-rbac-cm:

apiVersion: v1kind: ConfigMapmetadata:  name: argocd-rbac-cm  namespace: argocddata:  policy.default: role:readonly  policy.csv: |    p, role:org-admin, applications, *, */*, allow    p, role:org-admin, clusters, get, *, allow    p, role:org-admin, repositories, get, *, allow    p, role:org-admin, repositories, create, *, allow    p, role:org-admin, repositories, update, *, allow    p, role:org-admin, repositories, delete, *, allow    g, your-github-org:your-team, role:org-admin

Таким образом, пользователи, вошедшие в систему с помощью нашей системы единого входа GitHub, могут управлять приложениями и видеть кластеры.

Заключение

Установка, инициализация, управление (кластер, SSO, пользователь, RBAC), использование (создание приложения, развертывание) довольно просты. Я настоятельно рекомендую вам попробовать, в любом случае это займет у вас не больше часа. И я гарантирую, что вам понравится его простота и то, что он может вам принести.


Узнать подробнее о курсе Инфраструктурная платформа на основе Kubernetes.

Смотреть открытый вебинар Работа с NoSQL базами в k8s (на примере Apache Cassandra).

Подробнее..

Перевод Использование Google Protocol Buffers (protobuf) в Java

25.02.2021 20:10:38 | Автор: admin

Привет, хабровчане. В рамках курса "Java Developer. Professional" подготовили для вас перевод полезного материала.

Также приглашаем посетить открытый вебинар на тему gRPC для микросервисов или не REST-ом единым.


Недавно вышло третье издание книги "Effective Java" (Java: эффективное программирование), и мне было интересно, что появилось нового в этой классической книге по Java, так как предыдущее издание охватывало только Java 6. Очевидно, что появились совершенно новые разделы, связанные с Java 7, Java 8 и Java 9, такие как глава 7 "Lambdas and Streams" (Лямбда-выражения и потоки), раздел 9 "Prefer try-with-resources to try-finally" (в русском издании 2.9. Предпочитайте try-с-ресурсами использованию try-finally) и раздел 55 "Return optionals judiciously" (в русском издании 8.7. Возвращайте Optional с осторожностью). Но я был слегка удивлен, когда обнаружил новый раздел, не связанный с нововведениями в Java, а обусловленный изменениями в мире разработки программного обеспечения. Именно этот раздел 85 "Prefer alternatives to Java Serialization" (в русском издании 12.1 Предпочитайте альтернативы сериализации Java) и побудил меня написать данную статью об использовании Google Protocol Buffers в Java.

В разделе 85 "Prefer alternatives to Java Serialization" (12.1 Предпочитайте альтернативы сериализации Java) Джошуа Блох (Josh Bloch) выделяет жирным шрифтом следующие два утверждения, связанные с сериализацией в Java:

Лучший способ избежать проблем, связанных с сериализацией, никогда ничего не десериализовать.

Нет никаких оснований для использования сериализации Java в любой новой системе, которую вы пишете.

После описания в общих чертах проблем с десериализацией в Java и, сделав эти смелые заявления, Блох рекомендует использовать то, что он называет кроссплатформенным представлением структурированных данных (чтобы избежать путаницы, связанной с термином сериализация при обсуждении Java). Блох говорит, что основными решениями здесь являются JSON (JavaScript Object Notation) и Protocol Buffers (protobuf). Мне показалось интересным упоминание о Protocol Buffers, так как в последнее время я немного читал о них и игрался с ними. В интернете есть довольно много материалов по использованию JSON (даже в Java), в то время как осведомленность о Protocol Buffers среди java-разработчиков гораздо меньше. Поэтому я думаю, что статья об использовании Protocol Buffers в Java будет полезной.

На странице проекта Google Protocol Buffers описывается как не зависящий от языка и платформы расширяемый механизм для сериализации структурированных данных. Также там есть пояснение: Как XML, но меньше, быстрее и проще. И хотя одним из преимуществ Protocol Buffers является поддержка различных языков программирования, в этой статье речь пойдет исключительно про использование Protocol Buffers в Java.

Есть несколько полезных онлайн-ресурсов, связанных с Protocol Buffers, включая главную страницу проекта, страницу проекта protobuf на GitHub, proto3 Language Guide (также доступен proto2 Language Guide), туториал Protocol Buffer Basics: Java, руководство Java Generated Code Guide, API-документация Java API (Javadoc) Documentation, страница релизов Protocol Buffers и страница Maven-репозитория. Примеры в этой статье основаны на Protocol Buffers 3.5.1.

Использование Protocol Buffers в Java описано в туториале "Protocol Buffer Basics: Java". В нем рассматривается гораздо больше возможностей и вещей, которые необходимо учитывать в Java, по сравнению с тем, что я расскажу здесь. Первым шагом является определение формата Protocol Buffers, не зависящего от языка программирования. Он описывается в текстовом файле с расширением .proto. Для примера опишем формат протокола в файле album.proto, который показан в следующем листинге кода.

album.proto

syntax = "proto3";option java_outer_classname = "AlbumProtos";option java_package = "dustin.examples.protobuf";message Album {    string title = 1;    repeated string artist = 2;    int32 release_year = 3;    repeated string song_title = 4;}

Несмотря на простоту приведенного выше определения формата протокола, в нем присутствует довольно много информации. В первой строке явно указано, что используется proto3 вместо proto2, используемого по умолчанию, если явно ничего не указано. Две строки, начинающиеся с option, указывают параметры генерации Java-кода (имя генерируемого класса и пакет этого класса) и они нужны только при использовании Java.

Ключевое слово "message" определяет структуру "Album", которую мы хотим представить. В ней есть четыре поля, три из которых строки (string), а одно целое число (int32). Два из них могут присутствовать в сообщении более одного раза, так как для них указано зарезервированное слово repeated. Обратите внимание, что формат сообщения определяется независимо от Java за исключением двух option, которые определяют детали генерации Java-классов по данной спецификации.

Файл album.proto, приведенный выше, теперь необходимо скомпилировать в файл исходного класса Java (AlbumProtos.java в пакете dustin.examples.protobuf), который можно использовать для записи и чтения бинарного формата Protocol Buffers. Генерация файла исходного кода Java выполняется с помощью компилятора protoc, соответствующего вашей операционной системе. Я запускаю этот пример в Windows 10, поэтому я скачал и распаковал файл protoc-3.5.1-win32.zip. На изображении ниже показан мой запущенный protoc для album.proto с помощью команды protoc --proto_path=src --java_out=dist\generated album.proto

Перед запуском вышеуказанной команды я поместил файл album.proto в каталог src, на который указывает --proto_path, и создал пустой каталог build\generated для размещения сгенерированного исходного кода Java, что указано в параметре --java_out.

Сгенерированный Java-класс AlbumProtos.java содержит более 1000 строк, и я не буду приводить его здесь, он доступен на GitHub. Среди нескольких интересных моментов относительно сгенерированного кода я хотел бы отметить отсутствие выражений import (вместо них используются полные имена классов с пакетами). Более подробная информация об исходном коде Java, сгенерированном protoc, доступна в руководстве Java Generated Code. Важно отметить, что данный сгенерированный класс AlbumProtos пока никак не связан с моим Java-приложением, и сгенерирован исключительно из текстового файла album.proto, приведенного ранее.

Теперь исходный Java-код AlbumProtos надо добавить в вашем IDE в перечень исходного кода проекта. Или его можно использовать как библиотеку, скомпилировав в .class или .jar.

Прежде чем двигаться дальше, нам понадобится простой Java-класс для демонстрации Protocol Buffers. Для этого я буду использовать класс Album, который приведен ниже (код на GitHub).

Album.java

package dustin.examples.protobuf;import java.util.ArrayList;import java.util.List;/** * Music album. */public class Album {    private final String title;    private final List < String > artists;    private final int releaseYear;    private final List < String > songsTitles;    private Album(final String newTitle, final List < String > newArtists,        final int newYear, final List < String > newSongsTitles) {        title = newTitle;        artists = newArtists;        releaseYear = newYear;        songsTitles = newSongsTitles;    }    public String getTitle() {        return title;    }    public List < String > getArtists() {        return artists;    }    public int getReleaseYear() {        return releaseYear;    }    public List < String > getSongsTitles() {        return songsTitles;    }    @Override    public String toString() {        return "'" + title + "' (" + releaseYear + ") by " + artists + " features songs " + songsTitles;    }    /**     * Builder class for instantiating an instance of     * enclosing Album class.     */    public static class Builder {        private String title;        private ArrayList < String > artists = new ArrayList < > ();        private int releaseYear;        private ArrayList < String > songsTitles = new ArrayList < > ();        public Builder(final String newTitle, final int newReleaseYear) {            title = newTitle;            releaseYear = newReleaseYear;        }        public Builder songTitle(final String newSongTitle) {            songsTitles.add(newSongTitle);            return this;        }        public Builder songsTitles(final List < String > newSongsTitles) {            songsTitles.addAll(newSongsTitles);            return this;        }        public Builder artist(final String newArtist) {            artists.add(newArtist);            return this;        }        public Builder artists(final List < String > newArtists) {            artists.addAll(newArtists);            return this;        }        public Album build() {            return new Album(title, artists, releaseYear, songsTitles);        }    }}

Теперь у нас есть data-класс Album, Protocol Buffers-класс, представляющий этот Album (AlbumProtos.java) и мы готовы написать Java-приложение для "сериализации" информации об Album без использования Java-сериализации. Код приложения находится в классе AlbumDemo, полный код которого доступен на GitHub.

Создадим экземпляр Album с помощью следующего кода:

/** * Generates instance of Album to be used in demonstration. * * @return Instance of Album to be used in demonstration. */public Album generateAlbum(){   return new Album.Builder("Songs from the Big Chair", 1985)      .artist("Tears For Fears")      .songTitle("Shout")      .songTitle("The Working Hour")      .songTitle("Everybody Wants to Rule the World")      .songTitle("Mothers Talk")      .songTitle("I Believe")      .songTitle("Broken")      .songTitle("Head Over Heels")      .songTitle("Listen")      .build();}

Класс AlbumProtos, сгенерированныйProtocol Buffers, включает в себя вложенный класс AlbumProtos.Album, который используется для бинарной сериализации Album. Следующий листинг демонстрирует, как это делается.

final Album album = instance.generateAlbum();final AlbumProtos.Album albumMessage    = AlbumProtos.Album.newBuilder()        .setTitle(album.getTitle())        .addAllArtist(album.getArtists())        .setReleaseYear(album.getReleaseYear())        .addAllSongTitle(album.getSongsTitles())        .build();

Как видно из предыдущего примера, для заполнения иммутабельного экземпляра класса, сгенерированного Protocol Buffers, используется паттерн Строитель (Builder). Через ссылку экземпляр этого класса теперь можно легко преобразовать объект в бинарный вид Protocol Buffers, используя метод toByteArray(), как показано в следующем листинге:

final byte[] binaryAlbum = albumMessage.toByteArray();

Чтение массива byte[] обратно в экземпляр Album может быть выполнено следующим образом:

/** * Generates an instance of Album based on the provided * bytes array. * * @param binaryAlbum Bytes array that should represent an *    AlbumProtos.Album based on Google Protocol Buffers *    binary format. * @return Instance of Album based on the provided binary form *    of an Album; may be {@code null} if an error is encountered *    while trying to process the provided binary data. */public Album instantiateAlbumFromBinary(final byte[] binaryAlbum) {    Album album = null;    try {        final AlbumProtos.Album copiedAlbumProtos = AlbumProtos.Album.parseFrom(binaryAlbum);        final List <String> copiedArtists = copiedAlbumProtos.getArtistList();        final List <String> copiedSongsTitles = copiedAlbumProtos.getSongTitleList();        album = new Album.Builder(                copiedAlbumProtos.getTitle(), copiedAlbumProtos.getReleaseYear())            .artists(copiedArtists)            .songsTitles(copiedSongsTitles)            .build();    } catch (InvalidProtocolBufferException ipbe) {        out.println("ERROR: Unable to instantiate AlbumProtos.Album instance from provided binary data - " +            ipbe);    }    return album;}

Как вы заметили, при вызове статического метода parseFrom(byte []) может быть брошено проверяемое исключение InvalidProtocolBufferException. Для получения десериализованного экземпляра сгенерированного класса, по сути, нужна только одна строка, а остальной код это создание исходного класса Album из полученных данных.

Демонстрационный класс включает в себя две строки, которые выводят содержимое исходного экземпляра Album и экземпляра, полученного из бинарного представления. В них есть вызов метода System.identityHashCode() на обоих экземплярах, чтобы показать, что это разные объекты даже при совпадении их содержимого. Если этот код выполнить с примером Album, приведенным выше, то результат будет следующим:

BEFORE Album (1323165413): 'Songs from the Big Chair' (1985) by [Tears For Fears] features songs [Shout, The Working Hour, Everybody Wants to Rule the World, Mothers Talk, I Believe, Broken, Head Over Heels, Listen]AFTER Album (1880587981): 'Songs from the Big Chair' (1985) by [Tears For Fears] features songs [Shout, The Working Hour, Everybody Wants to Rule the World, Mothers Talk, I Believe, Broken, Head Over Heels, Listen]

Здесь мы видим, что в обоих экземплярах соответствующие поля одинаковы и эти два экземпляра действительно разные. При использовании Protocol Buffers, действительно, нужно сделать немного больше работы, чем припочти автоматическом механизме сериализации Java, когда надо просто наследоваться от интерфейса Serializable, но есть важные преимущества, которые оправдывают затраты. В третьем издании книги Effective Java (Java: эффективное программирование) Джошуа Блох обсуждает уязвимости безопасности, связанные со стандартной десериализацией в Java, и утверждает, что Нет никаких оснований для использования сериализации Java в любой новой системе, которую вы пишете.


Узнать подробнее о курсе "Java Developer. Professional".

Смотреть открытый вебинар на тему gRPC для микросервисов или не REST-ом единым.

Подробнее..

Перевод Распознаем речь с помощью IBM Speech-to-Text API

17.02.2021 18:10:52 | Автор: admin

Привет, Хабр. В рамках курса "Machine Learning. Advanced" подготовили для вас перевод интересного материала.

Также всех желающих приглашаем посмотреть открытый урок на тему
Multi-armed bandits для оптимизации AB тестирования.


Извлекаем разговоры из аудиозаписи с легкостью, используя Python.

В этой статье вы узнаете, как использовать IBM Speech to Text API для распознавания речи из файла аудиозаписи. Мы будем использовать бесплатную версию API, которая имеет некоторые ограничения, такие как, например, длина звукового файла. Подробнее об API я расскажу в этой статье чуть позже. Позвольте мне начать с предоставления вам некоторой справочной информации о применении распознавания речи в нашей повседневной жизни.

Предпосылки

Если вы читаете эту статью, я уверен, что вы знакомы с термином искусственный интеллект и осознаете его важность. Никого не повергнет в шок, если я скажу, что одно из лучших применений искусственного интеллекта в повседневной жизни это распознавание речи.

Распознавание речи из аудио позволяет нам как минимум экономить время мы говорим, вместо того, чтобы что-либо набирать. Это делает использование наших технологических устройств более увлекательным и простым. Эта технология также помогает нам взаимодействовать с этими устройствами без написания какого-либо кода. Представьте, что люди должны знать программирование, чтобы отдавать команды Алексе или Сири. Это было бы очень тупо.

Мне не терпится показать вам распознаватель речи в действии. Давай приступим к работе. Вот шаги, которым мы будем следовать в этом проекте.

Содержание:

  • Облачные сервисы распознавания речи

  • Шаг 1 - Библиотека

  • Шаг 2 - Импорт аудиоклипа

  • Шаг 3 - Определение распознавателя

  • Шаг 4 - Распознаватель речи в действии

  • Заключительный шаг - Экспорт результата

Облачные сервисы распознавания речи

Многие гигантские технологические компании имеют собственные распознавательные модели. Я поделюсь некоторыми из них здесь, чтобы вы увидели общую картину. Эти API-интерфейсы работают через облако и могут быть доступны из любой точки мира, если есть подключение к интернету. Кроме того, большинство из них являются платными, но их можно протестировать бесплатно. Например, Microsoft предлагает годовой бесплатный доступ для облачной учетной записи Azure.

Вот некоторые из наиболее популярных облачных сервисов преобразования речи в текст:

Шаг 1 Библиотека

Для этого проекта нам понадобится всего одна библиотека. И это SpeechRecognition. SpeechRecognition распространяется бесплатно с открытым исходным кодом. Она поддерживает несколько механизмов распознавания речи и API. Такие как; Microsoft Azure Speech, Google Cloud Speech, API IBM Watson Speech to Text и другие. В этом проекте мы будем тестировать IBM Watson Speech to Text API. Не стесняйтесь изучить исходный код и документацию пакета SpeechRecognition здесь.

Начнем с установки пакета. Мы собираемся использовать pip, менеджер библиотек Python.

pip install SpeechRecognition

После завершения процесса установки мы можем открыть наш редактор кода. Вы также можете использовать Jupyter Notebook.

import speech_recognition as s_r

Шаг 2 - Импорт

Я записал голосовую заметку с помощью компьютера. Он был в формате m4a, но распознаватель не работает с форматом m4a. Вот почему мне пришлось преобразовать его в wav формат.

audio_file = s_r.AudioFile('my_clip.wav')

Шаг 3 - Определение распознавателя

На этом этапе все, что мы сделаем, это определим распознаватель речи. Ранее мы импортировали библиотеку. А теперь мы создадим новую переменную и присвоим ей атрибут распознавания.

rcgnzr = s_r.Recognizer()

Шаг 4 - Распознаватель речи в действии

Пришло время действия! Мы запустим IBM speech to text на нашем аудиофайле. Перед запуском распознавателя я запущу функции, называемые adjust_for_ambient_noise и record, которые подавят шум и улучшат звук. Таким образом, наш распознаватель сможет выдавать более точные результаты.

with audio_file as source:    rcgnzr.adjust_for_ambient_noise(source)    clean_audio = rcgnzr.record(source)

Отлично, теперь у нас есть достаточно чистая аудиозапись. А теперь давайте запустим распознаватель речи IBM. (Мне потребовалось несколько часов, чтобы понять, как IBM Speech-to-Text API интегрируется с библиотекой Python SpeechRecogniton). Вот лучший способ вызвать распознаватель через API:

recognized_speech_ibm = r.recognize_ibm(clean_audio, username="apkikey", password= "your API Key")

Примечание: API IBM не работает без API-ключа. Нам нужно будет получить его на странице IBM Watson. Мне пришлось создать учетную запись для тестирования этой Speech-to-Text модели. Что мне понравилось в модели IBM, так это, что я могу обрабатывать 500 минут записей в месяц, используя триальную учетную запись, что более чем достаточно для учебных целей.

Последний шаг - экспорт результата.

Мы почти закончили. Пора проверить результат. Наш распознаватель обнаружил речь в аудиофайле на предыдущем шаге. Мы продолжим и проверим, как это сработало. Если результат нас устроит, мы экспортируем его в текстовый документ.

Чтобы проверить распознанную речь, выведем переменную с распознанным текстом:

print(recognized_speech_ibm)

Выглядит хорошо. Мой аудиофайл был распознан как надо. Я читал абзац из этой статьи. Если вы не удовлетворены результатом, есть много способов предварительной обработки аудиофайла для получения лучших результатов. Вот хорошая статья, в которой представлена более подробная информация о распознавании речи и о том, как повысить качество распознавания.

Теперь я экспортирую распознанную речь в текстовый документ. Мы увидим сообщение ready! в нашем терминале по завершении экспорта.

with open('recognized_speech.txt',mode ='w') as file:       file.write("Recognized Speech:")    file.write("\n")    file.write(recognized)    print("ready!")

Поздравляю! Если вы читаете этот абзац, вы создали распознаватель речи. Надеемся, что вам понравилось это практическое руководство и вы узнали что-то новое сегодня. Лучший способ практиковать свои навыки программирования заниматься интересными проектами. Я написал множеством других практических проектов, подобных этому. Не стесняйтесь обращаться ко мне, если у вас возникнут вопросы при реализации программы.

Подключайтесь. Посетите мой блог и YouTube, чтобы получить заряд вдохновения. Спасибо.


Узнать подробнее о курсе "Machine Learning. Advanced".

Посмотреть открытый урок на тему Multi-armed bandits для оптимизации AB тестирования.

Подробнее..

Перевод Новое тестирование фичей в Django 3.2

01.03.2021 20:22:05 | Автор: admin

Пару недель назад Django 3.2 выпустил свой первый альфа-релиз, а финальный релиз выйдет в апреле. Он содержит микс новых возможностей, о которых вы можете прочитать в примечаниях к релизу. Эта статья посвящена изменениям в тестировании, некоторые из которых можно получить на более ранних версиях Django с пакетами backport.

1. Изоляция setUpTestData()

В примечании к релизу говорится:

Объекты, назначенные для классификации атрибутов в TestCase.setUpTestData() теперь выделяются для каждого тестового метода.

setUpTestData() очень полезный прием для быстрого выполнения тестов, и это изменение делает его использование намного проще.

Прием TestCase.setUp() нередко используется из юнит-теста для создания экземпляров моделей, которые используются в каждом тесте:

from django.test import TestCasefrom example.core.models import Bookclass ExampleTests(TestCase):    def setUp(self):        self.book = Book.objects.create(title="Meditations")

Тест-раннер вызывает setUp() перед каждым тестом. Это дает простую изоляцию тестов, так как берутся свежие данные для каждого теста. Недостатком такого подхода является то, что setUp() запускается много раз, что при большом объеме данных или тестов может происходить довольно медленно.

setUpTestData() позволяет создавать данные на уровне класса один раз на TestCase. Его использование очень похоже на setUp(), только это метод класса:

from django.test import TestCasefrom example.core.models import Bookclass ExampleTests(TestCase):    @classmethod    def setUpTestData(cls):        cls.book = Book.objects.create(title="Meditations")

В промежутках между тестами Django продолжает откатывать (rollback) любые изменения в базе данных, поэтому они остаются там изолированными. К сожалению, до этого изменения в Django 3.2 rollback не происходили в памяти, поэтому любые изменения в экземплярах моделей сохранялись. Это означает, что тесты не были полностью изолированы.

Возьмем, к примеру, эти тесты:

from django.test import TestCasefrom example.core.models import Bookclass SetUpTestDataTests(TestCase):    @classmethod    def setUpTestData(cls):        cls.book = Book.objects.create(title="Meditations")    def test_that_changes_title(self):        self.book.title = "Antifragile"    def test_that_reads_title_from_db(self):        db_title = Book.objects.get().title        assert db_title == "Meditations"    def test_that_reads_in_memory_title(self):        assert self.book.title == "Meditations"

Если мы запустим их на Django 3.1, то финальный тест провалится:

$ ./manage.py test example.core.tests.test_setuptestdataCreating test database for alias 'default'...System check identified no issues (0 silenced)..F.======================================================================FAIL: test_that_reads_in_memory_title (example.core.tests.test_setuptestdata.SetUpTestDataTests)----------------------------------------------------------------------Traceback (most recent call last):  File "/.../example/core/tests/test_setuptestdata.py", line 19, in test_that_reads_in_memory_title    assert self.book.title == "Meditations"AssertionError----------------------------------------------------------------------Ran 3 tests in 0.002sFAILED (failures=1)Destroying test database for alias 'default'...

Это связано с тем, что in-memory изменение из test_that_changes_title() сохраняется между тестами. Это происходит в Django 3.2 за счет копирования объектов доступа в каждом тесте, поэтому в каждом тесте используется отдельная изолированная копия экземпляра модели in-memory. Теперь тесты проходят:

$ ./manage.py test example.core.tests.test_setuptestdataCreating test database for alias 'default'...System check identified no issues (0 silenced)....----------------------------------------------------------------------Ran 3 tests in 0.002sOKDestroying test database for alias 'default'...

Спасибо Simon Charette за изначальное создание этой функциональности в проекте django-testdata, и до его объединения в систему Django. На старых версиях Django вы можете использовать django тест-данные для той же изоляции, добавив декоратор@wrap_testdata в ваши методы setUpTestData(). Он очень удобен, и я добавлял его в каждый проект, над которым работал.

(В более старых версиях Django также документируется обходной путь, включающий повторный запрос базы данных в каждом тесте, но это медленнее).

2. Использование faulthandler по умолчанию

В примечании к релизу говорится:

DiscoverRunner сейчас использует faulthandler по умолчанию.

Это небольшое улучшение, которое может помочь вам дебаггить сбои низкого уровня. Модуль Python faulthandler предоставляет способ сброса экстренной трассировки в ответ на проблемы, которые приводят к сбою в работе интерпретатора Python.Тогда тестовый runner Django использует faulthandler. Подобное решение было скопировано из pytest, который делает то же самое.

Проблемы, которые ловит faulthandler, обычно возникают в библиотеках на языке C, например, в драйверах баз данных. Например, OS сигнал SIGSEGV указывает на ошибку сегментации, что означает попытку чтения памяти, не принадлежащей текущему процессу. Мы можем эмулировать это на Python, напрямую посылая сигнал самим себе:

import osimport signalfrom django.test import SimpleTestCaseclass FaulthandlerTests(SimpleTestCase):    def test_segv(self):        # Directly trigger the segmentation fault        # signal, which normally occurs due to        # unsafe memory access in C        os.kill(os.getpid(), signal.SIGSEGV)

Если мы делаем тест в Django 3.1, мы видим это:

$ ./manage.py test example.core.tests.test_faulthandlerSystem check identified no issues (0 silenced).[1]    31127 segmentation fault  ./manage.py test

Это нам не очень помогает, так как нет никакой зацепки на то, что вызвало ошибку сегментации.

Вместо этого мы видим трассировку на Django 3.2:

$ ./manage.py test example.core.tests.test_faulthandlerSystem check identified no issues (0 silenced).Fatal Python error: Segmentation faultCurrent thread 0x000000010ed1bdc0 (most recent call first):  File "/.../example/core/tests/test_faulthandler.py", line 12 in test_segv  File "/.../python3.9/unittest/case.py", line 550 in _callTestMethod  ...  File "/.../django/test/runner.py", line 668 in run_suite  ...  File "/..././manage.py", line 17 in main  File "/..././manage.py", line 21 in <module>[1]    31509 segmentation fault  ./manage.py test

( Сокращенно )

Faulthandler не может генерировать точно такую же трассировку, как стандартное исключение в Python, но он дает нам много информации для дибаггинга сбоя.

3. Timing (тайминг)

В примечании к релизу говорится:

DiscoverRunner теперь может трекать тайминг, включая настройку базы данных и общее время работы.

Команда manage.py test включает опцию --timing, которая активирует несколько строк вывода в конце пробного тест-запуска для подведения итогов по настройке базы данных и времени:

$ ./manage.py test --timingCreating test database for alias 'default'...System check identified no issues (0 silenced)....----------------------------------------------------------------------Ran 3 tests in 0.002sOKDestroying test database for alias 'default'...Total database setup took 0.019s  Creating 'default' took 0.019sTotal database teardown took 0.000sTotal run took 0.028s

Благодарим Ахмада А. Хуссейна за участие в этом мероприятии в рамках Google Summer of Code 2020.

Если вы используете pytest, опция --durations N работает схоже.

Из-за системы фикстуры pytest время настройки базы данных будет отображаться как время настройки (setup time) только для одного теста, что сделает этот тест более медленным, чем он есть на самом деле.

4. Обратный вызов (callbacks) тестаtransaction.on_commit()

В примечании к релизу говорится:

Новый метод TestCase.captureOnCommitCallbacks() собирает функции обратного вызова (callbacks functions), переданные в transaction.on_commit(). Это позволяет вам тестировать эти callbacks, не используя при этом более медленный TransactionTestCase.

Это вклад, который я ранее сделал и о котором ранее рассказывал.

Итак, представьте, что вы используете опцию ATOMIC_REQUESTSот Django, чтобы перевести каждый вид в транзакцию (а я думаю, что так и должно быть!). Затем вам нужно использовать функцию transaction.on_commit() для выполнения любых действий, которые зависят от того, насколько длительно данные хранятся в базе данных. Например, в этом простом представлении для формы контактов:

from django.db import transactionfrom django.views.decorators.http import require_http_methodsfrom example.core.models import ContactAttempt@require_http_methods(("POST",))def contact(request):    message = request.POST.get('message', '')    attempt = ContactAttempt.objects.create(message=message)    @transaction.on_commit    def send_email():        send_contact_form_email(attempt)    return redirect('/contact/success/')

Мы не посылаем сообщения по электронной почте, если только транзакция не сохраняет изменения, так как в противном случае в базе данных не будет ContactAttempt.

Это правильный способ написания подобного вида, но ранее было сложно протестировать callback (обратный вызов), переданный функции on_commit(). Django не будет запускать callback без сохранения транзакции, а его TestCase избегает сохранения, и вместо этого откатывает (rollback) транзакцию, так что это повторяется при каждом тесте.

Одним из решений является использование TransactionTestCase, которое позволяет сохранять транзакции кода. Но это намного медленнее, чем TestCase , так как он очищает все таблицы между тестами.

(Я ранее говорил об увеличении скорости в три раза благодаря конвертации тестов из TransactionTestCase в TestCase.)

Решением в Django 3.2 является новая функция captureOnCommitCallbacks(), которую мы используем в качестве контекстного менеджера. Она захватывает любые callbacks и позволяет вам добавлять утверждения или проверять их эффект. Мы можем использовать это, чтобы проверить наше мнение таким образом:

from django.core import mailfrom django.test import TestCasefrom example.core.models import ContactAttemptclass ContactTests(TestCase):    def test_post(self):        with self.captureOnCommitCallbacks(execute=True) as callbacks:            response = self.client.post(                "/contact/",                {"message": "I like your site"},            )        assert response.status_code == 302        assert response["location"] == "/contact/success/"        assert ContactAttempt.objects.get().message == "I like your site"        assert len(callbacks) == 1        assert len(mail.outbox) == 1        assert mail.outbox[0].subject == "Contact Form"        assert mail.outbox[0].body == "I like your site"

Итак, мы используем captureOnCommitCallbacks() по запросу тестового клиента на просмотр, передавая execute флаг, чтобы указать, что фальшивое сохранение (коммит) должно запускать все callbacks. Затем мы проверяем HTTP-ответ и состояние базы данных, прежде чем проверить электронную почту, отправленную обратным вызовом (callback). Наш тест затем покрывает все на просмотре, оставаясь быстрым и классным!

Чтобы использовать captureOnCommitCallbacks() в ранних версиях Django, установите django-capture-on-commit-callbacks.

5. Улучшенный assertQuerysetEqual()

В примечании к релизу говорится:

TransactionTestCase.assertQuerysetEqual() в данный момент поддерживает прямое сравнение с другой выборкой элементов запроса в Django

Если вы используете assertQuerysetEqual() в ваших тестах, это изменение точно улучшит вашу жизнь!

В дополнение к Django 3.2, assertQuerysetEqual() требует от вас сравнения с QuerySet после трансформации. Далее происходит переход по умолчанию к repr(). Таким образом, тесты, использующие его, обычно проходят список предварительно вычисленных repr() strings для вышеупомянутого сравнения:

from django.test import TestCasefrom example.core.models import Bookclass AssertQuerySetEqualTests(TestCase):    def test_comparison(self):        Book.objects.create(title="Meditations")        Book.objects.create(title="Antifragile")        self.assertQuerysetEqual(            Book.objects.order_by("title"),            ["<Book: Antifragile>", "<Book: Meditations>"],        )

Проверка требует немного больше размышлений о том, какие экземпляры моделей ожидаются. А также требует отработки тестов в случае изменения метода repr() модели.

Из Django 3.2 можно передать QuerySet или список объектов для сравнения, что позволяет упростить тест:

from django.test import TestCasefrom example.core.models import Bookclass AssertQuerySetEqualTests(TestCase):    def test_comparison(self):        book1 = Book.objects.create(title="Meditations")        book2 = Book.objects.create(title="Antifragile")        self.assertQuerysetEqual(            Book.objects.order_by("title"),            [book2, book1],        )

Спасибо Питеру Инглсби и Хасану Рамезани за то, что они внесли эти изменения. Они помогли улучшить тестовый набор Django.

Финал

Наслаждайтесь этими изменениями, когда Django 3.2 выйдет или сделайте это раньше через пакет backport.


Перевод статьи подготовлен в преддверии старта курса Web-разработчик на Python.

Также приглашаем всех желающих посмотреть открытый вебинар на тему Использование сторонних библиотек в django. На занятии мы рассмотрим общие принципы установки и использования сторонних библиотек вместе с django; а также научимся пользоваться несколькими популярными библиотеками: django-debug-toolbar, django-cms, django-cleanup и т.д.

Подробнее..

Тестирование скриншотами

03.03.2021 12:06:10 | Автор: admin

Для будущих студентов курса Python QA Engineer подготовили авторскую статью.

Также приглашаем на открытый вебинар по теме
Непрерывная интеграция с Jenkins. Рассмотрим, как настраивать автоматический запуск тестов, устанавливать плагины и создавать бекапы конфигураций сборок.


Здравствуйте! Сегодня хочу рассказать о нашем опыте тестирования скриншотами с использованием python, selenium, и Pillow.

Зачем? У нас был довольно большой (~1000) набор тестов на стеке python, pytest, selenium, которые отлично проверяли, что кнопки кликаются, а статистика отправляется (с использованием browserup proxy), но пропускали баги типа таких:

Используя только селениум, практически невозможно обнаружить такую неполадку, пока она не случится (не будешь же для каждого элемента добавлять проверку для каждого из его стилей).

Также проблема в том, что у seleniuma трудности с определением видимости некоторых компонентов, а значит, и протестировать верстку с его помощью затруднительно.

Посмотрим, как selenium определяет видимость для элементов карусели:

Скрипт:

from selenium.webdriver import Chromefrom collections import Counterdriver = Chrome()driver.get("https://go.mail.ru/search?q=%D1%86%D0%B2%D0%B5%D1%82%D0%BE%D1%87%D0%BA%D0%B8%20%D0%BA%D0%B0%D1%80%D1%82%D0%B8%D0%BD%D0%BA%D0%B8")elements = driver.find_elements_by_css_selector(".SmackPicturesContent-smackImageItem")print(Counter([el.is_displayed() for el in elements]))driver.quit()

Напечатает Counter({True: 10}), хотя видимых элементов явно не 10, и независимо от того, какой сегмент карусели отображается, количество видимых карточек не меняется, из-за чего невозможно проверить скролл.

Аналогично будут работать и явные ожидания (visibility_of, visibility_of_all_elements_located, etc), так как они просто дергают у элемента is_displayed.

Решение

Нам был нужен инструмент для pytest и selenium, чтобы мы могли переиспользовать инфраструктуру, на которой крутились текущие тесты. После поиска выяснилось, что ничего готового нет, а то что есть давно не поддерживается. Поэтому решили сделать сами.

В общем это работает так:

  • открываем страницу селениумом, готовим ее к тесту (заполняем поля, жмем кнопки);

  • скриншотим всю страницу, и вырезаем кусок, на котором размещен компонент, который нужно протестировать;

  • вырезаем его, повторяем все на стейджинге и попиксельно сравниваем получившиеся картинки;

  • сами картинки перед сравнением нарезаем на блоки размером 40х40 пикселей, те участки которые не совпали помечаем красным прямоугольником для наглядности.

Таким образом можно протестировать и развалившуюся верстку, и компоненты страницы, для которых селениум не может корректно определить видимость.

Рабочий пример на гитхабе.

В итоге тест выглядит так:

def test_search_block(self):   self.driver.get("https://go.mail.ru/")   def action():       self.driver.find_element_by_xpath("//span[contains(text(), 'Соцсети')]").click()   self.check_by_screenshot((By.CSS_SELECTOR, ".MainPage-verticalLinksWrapper"), action=action)

В action кладем все нужные сетапы, при необходимости финализацию можно добавить аналогичным способом.

После завершения теста у нас получится три изображения с тестинга, с прода и дифф. Все три добавляем в отчет.

Благодаря плагину для аллюра отчет выглядит так:

И в случае падения:

Проблемы

  1. Антиалайзинг особенно сильно флакали тесты, где на скриншот попадали svg. Решили смазав границу между цветами:

RED = "red"GREEN = "green"BLUE = "blue"ALPHA = "alpha"# https://github.com/rsmbl/Resemble.js/blob/dec5ae1cf1d10c9027a94400a81c17d025a9d3b6/resemble.js#L121# https://github.com/rsmbl/Resemble.js/blob/dec5ae1cf1d10c9027a94400a81c17d025a9d3b6/resemble.js#L981tolerance = {   RED: 32,   GREEN: 32,   BLUE: 32,   ALPHA: 32,}
def _is_color_similar(self, a, b, color):   """Проверить схожесть цветов. Для того, чтобы тесты не тригеррились на антиалиасинг допуски   в self.tolerance.   """   if a is None and b is None:       return True   diff = abs(a - b)   if diff == 0:       return True   elif diff < self.tolerance[color]:       return True   return False

Так же сделано в Resemble.js. Надо сказать, что это диалектически влияет на надежность тестов. С одной стороны мы можем упустить проблемы с одинаковыми цветами, с другой тесты практически перестают моргать из-за сглаживания.

Для особо тяжелых случаев просто удаляем проблемный элемент со страницы, например, так:

def test_search_block(self):   self.driver.get("https://go.mail.ru/")   def action():       element = self.driver.find_element_by_xpath("//span[contains(text(), 'Соцсети')]")       self.driver.execute_script("arguments[0].remove()", element)   self.check_by_screenshot((By.CSS_SELECTOR, ".MainVerticalsNav-listItemActive"), action=action)

Примерно так же можно добавить элементу заливку, удалить изображение, и т.д.

  1. На скриншотах появлялись затухающие скроллбары. Невозможно сделать скриншоты так, чтобы момент анимации у скроллов совпал из-за этого тесты сильно флакали. В итоге помогло включение хэдлесс режима скроллбары пропали.

  2. Не актуальные эталоны проблема решилась сама собой благодаря отказу от эталонов. Заодно нет проблемы с необходимостью следить за тем, чтобы эталоны были сделаны там же, где гоняются тесты (на разных платформах разные шрифты и не только).

  3. Для мобильных тестов (андроид эмуляторы и эмуляция в хроме) нужно учитывать плотность пикселей. Так получаем размеры и координаты элемента:

def _get_raw_coords_by_locator(self, locator_type, query_string):   """Без учета плотности пикселей."""   wait = WebDriverWait(self.driver, timeout=10, ignored_exceptions=Exception)   wait.until(lambda _: self.driver.find_element(locator_type, query_string).is_displayed(),                   message="Невозможно получить размеры элемента, элемент не отображается")     el = self.driver.find_element(locator_type, query_string)   location = el.location   size = el.size   x = location["x"]   y = location["y"]   width = location["x"] + size['width']   height = location["y"] + size['height']   return x, y, width, height

А так делаем поправку на плотность пикселей:

def _get_coords_by_locator(self, locator_type, query_string) -> Tuple[int, int, int, int]:   x, y, width, height = self._get_raw_coords_by_locator(locator_type, query_string)   return x * self.pixel_ratio, y * self.pixel_ratio, width * self.pixel_ratio, height * self.pixel_ratio

Если не учесть этого, скриншотится будет все что угодно, кроме нужных элементов.

Размер элементов, которые возвращает селениум:

from selenium.webdriver import Chrome, ChromeOptionsoptions = ChromeOptions()options.add_experimental_option("mobileEmulation", {'deviceName': "Nexus 5"})options.add_argument('--headless')caps = options.to_capabilities()driver = Chrome(desired_capabilities=caps)driver.get("https://go.mail.ru/")print(driver.find_element_by_xpath("//body").size)driver.save_screenshot("test.png")driver.quit()

Напечатает: {'height': 640, 'width': 360} для боди страницы.

И в тоже время вернет скриншот размером 1080 х 1920:

 file test.pngtest.png: PNG image data, 1080 x 1920, 8-bit/color RGBA, non-interlaced

4.Тесты никогда не будут зелеными во время релиза. Любые изменения в покрытых компонентах заставят тесты покраснеть, или сделают их сравнение невозможным (если расползутся размеры на тестинге и стейджинге). У этого есть и плюс: на диффе в отчете сразу видны изменения, в том числе те, которых не ожидали.

Итог

Сейчас у нас ~570 скриншот-тестов в двух сборках (мобильная и десктопная). Каждая сборка запущена в 20 потоков, и идет около 15 минут. С учетом изменений в релизах, которых ломают тесты, флакающих не больше 2-3%. В основном тесты падают из-за регресса, или изменений в верстке. Писать их тоже достаточно легко, при необходимости проверку скриншотами можно совместить с обычными для селениум-тестов проверками (текст, кликабельность, видимость).

Полезные ссылки про тестирование скриншотами:

  1. https://blog.rinatussenov.com/automating-manual-visual-regression-tests-with-python-and-selenium-be66be950196

  2. https://www.youtube.com/watch?v=crbwyGlcXm0


Узнать подробнее о курсе Python QA Engineer.

Смотреть открытый вебинар по теме Непрерывная интеграция с Jenkins.

Подробнее..

Перевод Секционирование таблиц и время компиляции плана запроса в SQL Server

01.03.2021 20:22:05 | Автор: admin

Для будущих учащихся на курсе "MS SQL Server Developer" подготовили перевод полезной статьи.

Также приглашаем всех желающих на открытый вебинар на тему
Polybase: жизнь до и после. На вебинаре рассмотрим, как можно было взаимодействовать с другими базами данных до Polybase, и как это работает сейчас.


Меня иногда спрашивают: Если в таблице много индексов и SQL Server вынужден анализировать больше вариантов, то не замедлит ли это построение плана запроса?

Что же, вполне может быть, но что действительно сбивает с толку оптимизатор, так это секционирование. Даже простейшие запросы к секционированным таблицам могут привести к значительному увеличению использования процессорного времени. Хуже того, как однажды сказал знаменитый философ: Больше секционирования больше проблем.

Итак, давайте возьмем какую-нибудь из баз данных Stack Overflow и создадим функцию секционирования, которая будет разбивать наши данные по дням:

USE StackOverflow;GO/* Create date partition function by day since Stack Overflow's origin,modified from Microsoft Books Online: https://docs.microsoft.com/en-us/sql/t-sql/statements/create-partition-function-transact-sql?view=sql-server-ver15#BKMK_examples DROP PARTITION SCHEME [DatePartitionScheme];DROP PARTITION FUNCTION [DatePartitionFunction];*/DECLARE @DatePartitionFunction nvarchar(max) =     N'CREATE PARTITION FUNCTION DatePartitionFunction (datetime)     AS RANGE RIGHT FOR VALUES (';  DECLARE @i datetime = '2008-06-01';WHILE @i <= GETDATE()BEGIN  SET @DatePartitionFunction += '''' + CAST(@i as nvarchar(20)) + '''' + N', ';  SET @i = DATEADD(DAY, 1, @i);  END  SET @DatePartitionFunction += '''' + CAST(@i as nvarchar(20))+ '''' + N');';  EXEC sp_executesql @DatePartitionFunction;  GO   /* Create matching partition scheme, but put everything in Primary: */CREATE PARTITION SCHEME DatePartitionScheme  AS PARTITION DatePartitionFunction  ALL TO ( [PRIMARY] ); GO

Далее создадим секционированную копию таблицы Users, разбив ее по значениям в колонке CreationDate:

DROP TABLE IF EXISTS dbo.Users_partitioned;GOCREATE TABLE [dbo].[Users_partitioned]([Id] [int] NOT NULL,[AboutMe] [nvarchar](max) NULL,[Age] [int] NULL,[CreationDate] [datetime] NOT NULL,[DisplayName] [nvarchar](40) NOT NULL,[DownVotes] [int] NOT NULL,[EmailHash] [nvarchar](40) NULL,[LastAccessDate] [datetime] NOT NULL,[Location] [nvarchar](100) NULL,[Reputation] [int] NOT NULL,[UpVotes] [int] NOT NULL,[Views] [int] NOT NULL,[WebsiteUrl] [nvarchar](200) NULL,[AccountId] [int] NULL) ON [PRIMARY];GO CREATE CLUSTERED INDEX CreationDate_Id ON dbo.Users_partitioned (Id)ON DatePartitionScheme(CreationDate);GO INSERT INTO dbo.Users_partitioned (Id, AboutMe, Age,CreationDate, DisplayName, DownVotes, EmailHash,LastAccessDate, Location, Reputation, UpVotes,Views, WebsiteUrl, AccountId)SELECT Id, AboutMe, Age,CreationDate, DisplayName, DownVotes, EmailHash,LastAccessDate, Location, Reputation, UpVotes,Views, WebsiteUrl, AccountIdFROM dbo.Users;GOLets c

Сравним производительность с секционированием и без

Создадим некластеризованный индекс для таблиц Users и Users_partitioned. Обратите внимание, даже если при создании индекса для Users_partitioned не указать, что он секционирован, то он все равно будет секционирован по умолчанию, что весьма замечательно:

CREATE INDEX DisplayName ON dbo.Users(DisplayName);CREATE INDEX DisplayName ON dbo.Users_partitioned(DisplayName);

Теперь выполним чрезвычайно простой запрос, который возвращает одну строку:

SET STATISTICS TIME, IO ON;SELECT * FROM dbo.Users WHERE DisplayName = N'Brent Ozar';GOSELECT * FROM dbo.Users_partitioned WHERE DisplayName = N'Brent Ozar';GO

Планы запросов, на первый взгляд, выглядят одинаково, но обратите внимание, что стоимость запроса к несекционированной таблице составляет 0% от общей стоимости, а к секционированной 100%:

Это связано с тем, что предполагаемая стоимость запроса к несекционированной таблице значительно меньше 0,001, а оценка стоимости запроса к секционированной более 15. Что еще хуже, время компиляции (compile time), время выполнения (execution time) и количество логических чтений (logical reads) совершенно разные. На скриншоте ниже в верхней части показана статистика несекционированного запроса, а внизу секционированного (для удобочитаемости из вывода убрана лишняя информация):

И только на компиляцию плана к секционированной таблице ушло 27 мс процессорного времени. Я знаю, о чем вы подумали: Кого волнуют 27 мс процессорного времени? Но вспомните у нас был очень простой запрос! В реальной жизни вполне нормально, когда на составление плана уходит более 250 мс процессорного времени. Это означает, что в секунду на одном ядре процессора вы сможете скомпилировать только четыре запроса. Вот когда загрязнение кеша планов из-за непараметризированных запросов действительно портит вам жизнь.

Написать этот пост меня побудил один из моих клиентов, который решил секционировать все таблицы независимо от их размера при этом использовал везде одинаковую гранулярность секций, чтобы его динамические ETL-запросы остались простыми. К сожалению, даже запросы к простым таблицам конфигурации занимали более 250 мс процессорного времени только для компиляции плана.

Хорошо, но как насчет несекционированных индексов?

Давайте удалим секционированный индекс для таблицы Users_partitioned и создадим несекционированный. Обратите внимание, что для секционированной таблицы необходимо указать ON PRIMARY в качестве файловой группы, иначе любой некластеризованный индекс по умолчанию также будет автоматически секционирован.

CREATE INDEX DisplayName ON dbo.Users(DisplayName);CREATE INDEX DisplayName ON dbo.Users_partitioned(DisplayName) ON [PRIMARY];

И выполним запросы еще раз:

SET STATISTICS TIME, IO ON;SELECT * FROM dbo.Users WHERE DisplayName = N'Brent Ozar';GOSELECT * FROM dbo.Users_partitioned WHERE DisplayName = N'Brent Ozar';GO

Теперь оценки стоимостей одинаковые:

Но все-таки это только оценка. Единственное, что здесь совпадает это логические чтения:

Компиляция и выполнение плана запроса занимают больше времени, если какой-либо из задействованных объектов секционирован

Эти накладные расходы невелики, если сравнивать их с накладными расходами запросов к большим хранилищам данных, когда секционирование может уменьшить количество чтений. Но если сравнивать с небольшими объектами (например, rowstore-индексы размером до 100 ГБ), к которым часто обращаются с разнообразными запросами, требующими построения новых планов выполнения, то тогда накладные расходы на секционирование уже начинают суммироваться. Чем больше секционированных объектов, тем больше секций в каждом объекте, тем больше проблем.

Секционирование одна из тех возможностей, о которой я никогда не слышал: Вау, что бы я не сделал, секционирование всегда улучшает производительность! Скорее всего, об этом продолжат говорить: Я понятия не имел, что секционирование может создать проблемы и ТАМ.


Узнать подробнее о курсе "MS SQL Server Developer".

Смотреть открытый вебинар Polybase: жизнь до и после.

Подробнее..

Исполняемый обвес

04.03.2021 20:15:20 | Автор: admin

Привет, хабровчане. Для будущих студентов курса Reverse-Engineering. Basic Александр Колесников подготовил полезную статью.

Также приглашаем всех желающих посетить открытый вебинар на тему Эксплуатация уязвимостей в драйвере. В первой части вебинара будет пример классической уязвимости переполнения буфера, а также уязвимости переполнения целочисленного типа. Также участники вместе с экспертом разберут особенности разработки эксплойтов в режиме ядра.


Статья будет разбита на 2 части теоретический минимум для понимания основных элементов навесных защит исполняемых файлов, и вторая, которая покажет несколько примеров разбора файлов. Все данные не претендуют на полноту. Для полного понимания темы и проведения распаковки файлов, которые были защищены рассматриваемыми защитами, нужно терпение и достаточный бэкграунд в ОС Internals.

Disclamer: Вся информация предоставляется исключительно для обучающих целей.

Навесная защита

Используемый термин навесная защита на самом деле используется только для обозначения класса пакеров, основная цель которых не дать реверс инженеру или начинающему исследователю быстро разобраться с алгоритмом и модифицировать его или скопировать. Ключевое слово здесь быстро. Способы достижения этой цели любые, вплоть до блокирования и обнаружения любых инструментов для динамической инструментации и отладки. Давайте попробуем описать спектр возможностей:

  • Охранные процедуры, которые детектируют присутствие отладчика в системе;

  • Охранные процедуры для детектирования песочницы;

  • Охранные процедуры для детектирования динамической инструментации;

  • Блокирование возможностей отладки охраняемого процесса;

  • Виртуальная машина с собственным набором команд;

  • Методы обфускации и запутывания кода имитовставки, наномиты.

Список внушителен, все это заставить работать для защищаемого приложения достаточно сложная задача, но тем не менее не невыполнимая. На сегодняшний день такие защиты существуют и довольно успешно применяются. Примерами может служить WinLicense, Themida, Enigma Protector.

Все перечисленные системы коммерческие и применяются для лицензирования и защиты приложений. Эти пакеры безусловно созданы для благих целей, чтобы труд разработчиков программного обеспечения был надежно защищен, но технологии подобного рода так же могут быть использованы для упаковки вредоносного программного обеспечения. Причем, могут применяться именно перечисленные пакеры, а могут быть частичные собственные имплементации. Одним из последних и ярких примеров может быть FinSpy RAT, который как раз был защищен своей собственной имплементацией навесной защиты.

Краеугольный камень всех перечисленных выше защит кастомная виртуальная машина. Трюк, который математически можно описать как модифицированный конесный автомат. Именно этот элемент всегда вызывает головную боль у начинающих реверс инженеров. Как анализировать подобные механизмы? Какие инструменты применять? Попробуем разобраться.

Методы исследования

Что такое виртуальная машина для защиты кода? Это алгоритм, который оперирует своими собственными версиями команд, которые могут отличаться от команд процессора, на котором запускается приложение. Существуют 2 типа виртуальных машин:

  • Виртуальная машина, которая преобразует команды исполняемого файла, сохраняет их в файл. При запуске такого приложения команды расцифровываются в команды процессора и приложение работает как обычно.

  • Виртуальная машина содержит набор команд, которые используются для выполнения алгоритма защищаемого приложения. Защищаемое приложение преобразуется в последовательность команд, которые может разобрать только виртуальная машина, и эти команды используются для выполнения всех операций защищаемого приложения. То есть нет совершенно момента времени, когда команды виртуальной машины снова становятся обычными командами процессора. Такие виртуальные машины иногда называют виртуальными обфускаторами.

Обычная архитектура виртуальной машины со своим набором команд имеет следующие отличительные черты:

  • Алгоритм инициализации виртуальной машины;

  • Огромный switch блок, который имплементирует каждую команду виртуальной машины;

  • Большой блок данных, который содержится в файле и не поддается декомпиляции. Обычно это байткод, который используется виртуальной машиной. Themida, к примеру, может использовать 4 разных вида байткода. При упаковке приложения пользователь может выбрать, какой вариант байткода будет использоваться.

Все действия, которые могло бы выполнять защищаемое приложение на самом низком уровне выполняются за счет вызова команд из switch блока виртуальной машины. Как эти блоки могут выглядеть в декомпиллированной версии, можно видеть на снимках ниже:

Картинка позаимствована отсюда.

Как такое анализировать? Общий алгоритм анализа сводится к:

  • Снятие всех антиотладочных приемов;

  • Идентификация места сохранения всех регистров в структуру для начала работы виртуальной машины;

  • Идентификация используемого набора команд;

  • Идентификация используемого байткода;

  • Идентификация типа виртуальной машины (расшифровывается ли код до native в памяти и затем работает как обычно или нет);

  • Создание декодировщика для работы с идентифицированным байткодом.

Проведем небольшую практику.

Начало пути

В качестве примера будем использовать древний VMProtect 1.1 версии. Соберем простое приложение и обфусцируем его. Исходный код приложения:

#include <iostream>int main{  std::cout<< "Hello World!" <<std::endl;  system("pause");  return 0;}

Приложение соберем в release варианте и декомпилируем. В итоге Main функция будет выглядеть так:

А после преобразования через VMProtect:

Так как команд вообщем-то не так много, то и на графе мы видим не огромный switch блок, а всего лишь несколько дополнительных блоков.

Пройдемся по каждому из них:

Первые два блока выполняют функцию первых шагов инициализации. В этих шагах сохраняются регистры общего назначения в общую струтуру для более удобного использования и так же производится инициализация указателя на набор байткода, который будет использоваться для работы алгоритма приложения.

Последующие два блока созданы для запутывания алгоритма, это и есть слабая обфускация, которая просто замыливает глаз. Основные действия выполняются дальше, в последнем блоке этой функции:

Последний блок позволяет инициализировать edx регистр адресом функции обработчика и алгоритм вытягивается через выполнение виртальной машиной байткода.

Кстати, байткод выглядит так:

В следующей статье познакомимся с тем как раскодировать команды и провести отладку такого приложения.


Узнать подробнее о курсе Reverse-Engineering. Basic.

Смотреть открытый вебинар на тему Эксплуатация уязвимостей в драйвере.

Подробнее..

Хранимая процедура с возвращаемыми значениями в SSIS

05.03.2021 18:07:04 | Автор: admin

Для будущих учащихся на курсе "MS SQL Server Developer" преподаватель и эксперт по базам данных Евгений Туркестанов подготовил полезную статью.

Приглашаем также на открытый вебинар по теме
Polybase: жизнь до и после. На занятии участники вместе с экспертом рассмотрят, как можно было взаимодействовать с другими базами данных до Polybase, и как это работает сейчас.


При всем моем двадцатилетнем опыте работы с MS SQL Server и SSIS (когда-то еще DTS), никогда не любил хранимые процедуры с возвращаемым значением. Не знаю, почему так сложилось. Может быть потому, что хранимки чаще приходилось использовать для реализации какой-то логики или возвращении набора записей, а для получения одного значения применял функции. Ну, так вот сложилось. Подразумеваю, что нелюбовь эта взаимная, что подтвердилось в последнем проекте, где хоть убей, но надо было с SSIS присваивать переменным возвращаемые значения процедуры. Изначально, пакет был не мой, а другого разработчика. Ничего плохого говорить не буду, все было сделано достаточно грамотно.

Процедура возвращала два значения типа DATETIME. В процессе работы я наступил на некоторые грабли, что и сподвигнуло на написание этой статьи.

Итак, дано:

1. Пакет SSIS

2. Переменные пакета:

Пакет, естественно, привожу тестовый, но сделанный по аналогу. Извините, но оригинальный, к сожалению, показать не могу.

3. Есть процедура, которая забирает две даты из таблицы. Даты нужны для определения интервалов инкрементальной заливки хранилища базы. Ниже приводится упрощенный текст процедуры:

Как видно, ничего сложного тут нет, даже слишком все просто. Можно было, в принципе, не делать возвращаемые параметры, а просто вернуть значения. Но, хозяин барин, что есть, то есть. Если мы запустим процедуру, то результат будет такой:

Теперь наш пакет.

Берем Execute SQL Command задание, настройки:

Как видим, возвращающий результат не выбран, так как у нас возвращаемые параметры. В выражение SQL ставим нашу процедуру:

И теперь назначаем параметры. Здесь самое интересное.

Для возвращаемых параметров выбираем Output и переменные пакета. Вопрос какой тип данных для этих параметров мы должны выбрать?

Для проверки того, что будет возвращаться, я создал скрипт задание, который будет показывать C# MessageBox со значением переменных. Настройки и код такие:

Вернемся к нашим параметрам, вернее, к их типу. Какой тип выбрать? У возвращаемых параметров процедуры тип DATETIME. Посмотрим, что предлагает SSIS.

SSIS package "F:\Projects\SSIS\TestMultiplePutput\TestMultiplePutput\TestMultiplePutput.dtsx" starting.Error: 0xC002F210 at Execute SQL Task, Execute SQL Task: Executing the query " exec dbo.testMultipleOutput ?, ? OUTPUT, ? OUTPUT" failed with the following error: "Error HRESULT E_FAIL has been returned from a call to a COM component.". Possible failure reasons: Problems with the query, "ResultSet" property not set correctly, parameters not set correctly, or connection not established correctly.Task failed: Execute SQL TaskWarning: 0x80019002 at TestMultiplePutput: SSIS Warning Code DTS_W_MAXIMUMERRORCOUNTREACHED.  The Execution method succeeded, but the number of errors raised (1) reached the maximum allowed (1); resulting in failure. This occurs when the number of errors reaches the number specified in MaximumErrorCount. Change the MaximumErrorCount or fix the errors.SSIS package "F:\Projects\SSIS\TestMultiplePutput\TestMultiplePutput\TestMultiplePutput.dtsx" finished: Failure.The program '[50800] DtsDebugHost.exe: DTS' has exited with code 0 (0x0)

Интересно. Причем ошибка вылезает из COM компонента проблемы с запросом, не настроен результат или параметры. Но, как мы видели, процедура отрабатывает. Если выражение просто скопировать в SSMS, поменять параметры и запустить, все работает. Честно говоря, ошибка меня заставила потерять какое-то время, но вменяемого ответа почему так происходит, я не нашел. Возможно, здесь происходит ошибка конвертации DATETIME в DATE. Причем, это происходило только, если я использовал OUTPUT параметры в компоненте.

Сам пакет выглядит таким образом:

Все настолько просто, что даже скучно. Смотрим дальше.

Выбираю DBDATE, сохраняю, запускаю. Как и следовало ожидать, вернулась дата.

DBTIME выбирать смысла нет, поэтому идем дальше. Хотя я все-таки попробовал. Вернулось ожидаемое время, но с сегодняшней датой. То же самое произошло и с DBTIME2. Осталось еще пара типов.

DBTIMESTAMP. Кто-то мне говорил, что не стоит с этим типом работать в SSIS. Дескать, неправильно отображает дату и время, так как это не совсем DATETIME. Сейчас мы это увидим. Вернулось то, что и ожидалось:

Если выбрать, ради эксперимента, последний временной тип DBTIMESTAMPOFFSET, который, в принципе, предназначен для работы с временными зонами, но чем черт не шутит, то он вернет правильную дату, но другое время:

Есть еще один вариант работы с возвращаемыми параметрами, но это, скажем, на любителя или для тех, кто сильно, как и я их не любит. В Execute SQL Command в тексте можно написать выражение T-SQL, примерно такое:

И настроить возвращаемый результат

И определить переменные для присвоения значений

Как говорится, дело вкуса. По производительности разницы нет, по стилю и самому решению, мне, даже при всей нелюбви к возвращаемым параметрам в пакете, больше импонирует первый вариант.


Узнать подробнее о курсе "MS SQL Server Developer".

Смотреть открытый вебинар по теме Polybase: жизнь до и после.

Подробнее..

Категории

Последние комментарии

© 2006-2021, personeltest.ru